Our approach on how to gain more certainty about what to expect from both ends of an API

As in many companies, our team combines both front-end and backend developers, more or less keen on residing in their respective engineering domain. We have a Java backend exposing a REST-API that is consumed by an Apollo GraphQL server. These two services need to agree on an API. To avoid too much conversational incentive, in usually short meetings we define the API’s endpoints.

Often enough, we face problems when the agreement made is not entirely followed through or interpreted differently by one of the involved parties. This was our motivation to come up with a way to achieve certainty about what is to be expected on both ends of the deal.

I am a developer in Signavio’s Process Intelligence product team and I want to present you an idea how to attain said certainty. Basically, how to type an API end-to-end.

There are several things constantly enhancing my desire to work as a developer. Few beat my affection for Slack bots and seemingly random acronyms. Unsurprisingly, the project’s name was created long before most of the Confluence page mapping out its future approach.

SWAGSTER: Swagger Safe Typing Evaluation Repository.

corresponding slack emoji

For everyone not familiar with Swagger.io, it is essentially a library that allows you to annotate your API-related Java code to later on generate fancy overviews amongst other probably more relevant features (e.g. converts well to Postman).

With the generous help of a Gradle plug-in simply called swagger-gradle-plugin and a corresponding Gradle task, we are able to generate an OpenAPI JSON that is easily understood by Swagster.

resolve {
    outputFileName = 'open_api_schema'
    outputFormat = 'JSON'
    prettyPrint = 'TRUE'
// ...
}

In our Node.js clad GraphQL server, we needed to get rid of the uncertainty JavaScript tends to provide in regards of typing, replacing it with another popular superset, namely TypeScript.

After freshly and therefore continuously creating the OpenAPI JSON in our backend’s CI pipeline, we use git to check in the JSON to the Swagster repo, where a CircleCI build job is triggered we mainly abuse as its remote interface, activating the pipeline in Swagster.

Now, the fun part begins

openapi-diff is an npm package designed to differentiate between two given files. Don’t be fooled by their documentation, you can run it just as much from your Node.js environment as from the command line. We make use of that letting it identify for us whether changes were made to the API.

If this turns out positive, we utilize the ts-morph package to convert the transmitted OpenAPI JSON file to TypeScript through code generation. There, we extract the endpoints’ information putting it into the functions of a new class extending the RESTDataSource provided by Apollo. The RESTDataSource comes with caching, reduction of redundancies and error handling out of the box, which is obviously helpful.

Example

Take this as part of the API specification in your OpenAPI file:

"paths" : {
    "/houseelf/{houseelfid}" : {
      "get" : {
        "operationId" : "getHouseElf",
        "parameters" : [ {
          "name" : "houseelfid",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string"
          }
        } ],
      "responses" : {
          "default" : {
            "description" : "default response",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/HouseElf"
                }
              }
            }
          }
        }
      }
    }
}

This is how it then looks like within our RestDataSource class:

/*
 *  GET '/houseelf/{houseelfid}'
 */
getHouseElf(houseelfid: string): Promise<Components.Schemas.HouseElf> {
    return this.get(`/houseelf/${houseelfid}`)
}

At the moment, we have our types produced by the npm package dtsgenerator as in here:

yarn dtsgen --out src/types.d.ts src/open_api_schema.json

Meanwhile a Slack message is generated enumerating the endpoints that underwent alteration, which is then send to our monitoring channel. I normally add some Rick and Morty emojis here for good measure.

Eventually, the newly generated TypeScript code is published to both the Swagster repository on git and npm.

custom graphic

Outcome

Your API changes highly increase in visibility being reported to Slack. Also reminding you to manually update the GraphQL server’s Swagster npm package to apply its output to the GraphQL resolvers.

There it comes in handy to experience type warnings through TypeScript in case you missed a change to the API.

Swagster immensely reduces boiler plate code in the GraphQL server and also feels inherently safe with code generation being the means of implementation.

Thereby successfully concluding the type safety certainty effort from backend to GraphQL server to frontend.