Automatic Generating Flow Types from Swagger (a.k.a. OAS) in Javascript Projects

V Kolobkov
8 min readDec 28, 2019

When you develop complex web application, API is the thread that sews different parts of it together. Good API specification documents API structure and enables developers to think of it in details before they really start to code anything (thus reducing the time needed to re-work it), foresee some caveats in advance, and avoid misunderstanding between developers and entire teams in future. As API specification describes data structures used in the system, it enables developers to define them in code as data types and harness the power of strong typing.

Of course, strong typing has its pros and cons, supporters and haters. Let’s leave reasoning it out apart from this article, same as comparing Flow with TypeScript, these are good subjects for separate discussion.

To make it a bit easier, special tools exist that can create such types out of API specification automatically. In this article, we’ll try to set up such tool for Swagger (a.k.a. OAS, or OpenAPI Specification, in newer version) API specification in javascript project. As javascript is weakly-typed language, we’ll use Flow type checker to bring magic of types into it.

TLDR: we host our API specifications so that they can be accessed via HTTP, and then set up our project to download them and generate Flow types with some CLI tool (I’ve picked swagger-to-flowtype npm package for that).

The working proof-of-concept (POC) project implementing the described concepts is available in https://github.com/kolobok86/openapi-to-flow-example repository.

Storing API specifications

Before we start, we need to place API specifications somewhere where they can be easily accessed by developers, and updated if needed. In this article, I suggest to host them in the web, so that they could be downloaded over HTTP by URL link. The reason to do so (rather then store them in GitHub repository, company’s shared network directory or whatever else) is that HTTP is simple and common communication way for different platforms (unlike, say, network sharing, that may be cumbersome when developers with Linux and Windows PC’s work in same environment). Also, when several teams are involved into development (say, frontend and backend teams often use same API’s, but different tools to implement them), it’s good to have single place to update specification, rather then commit it in each repository. Placing API’s specs in HTTP-accessible endpoint is easy and convenient way to solve this problem, no matter is it hosted in Internet either your company’s internal network.

To me, personally, good place to store the specifications is SwaggerHub . That’s handy online tool to manage (that say: create, store, share, edit and control versions) API specifications for your team. For personal use, it has free plan that enables you to have 3 public and 1 private API definition, which is quite not bad for pet projects. Though, definitely, these limits won’t satisfy needs of professional teams who will need a paid plan.

Among other features, once API specification is created in SwaggerHub, it can be downloaded by direct URL. Detailed how-to is in their docs here . Shortly, you copy URL of the page where you edit your specification in browser’s address string, change “app” to “api in “app.swaggerhub.com” hostname, and that will be the URL for downloading the specification. Private API’s require a secret key to be provided in addition, we’ll get back to it below in this article.

Of course, you are free to use tool of your choise to host API specifications, such as your own HTTP server (such as Apache or Nginx), Amazon S3 Storage, GitHub Pages, etc. — they all fit this purpose.

Generating Flow types

Now, when our API specifications can be accessed by HTTP, let’s arrange our project to fecth them automatically, and save locally into project’s directory. Also, after downloaded and saved, these specifications need to be added to your Version Control System (VCS), that simplifies comparing changes in specifications in different branches, when they rely on different versions of the specification.

Please note: this structure does not mean that any API change made on HTTP host will be propagated to all repositories automatically. Your team will need to decide how to notify members about API updates — online chat, emails, daily meetings or whatever (similarly to case, when important changes are made in master branch and developers should merge them into branches they’re currently working on). But it will enable developers to grab the updates and generate new types in one command.

In Unix-based operation systems, the common way to download something via command line and save it locally is good old cURL. But in Windows, it is not available by default. So, we simply implement this functionality in javascript, to be cross-platform and bring some custom logic. Please look at ./typeUtils/generate-flow-types.js file in the POC repo (mentioned above), it implements all the required functionality.

Shortly, the module downloads and saves the specification(s) locally. It is implemented trivially basing on code snippet from nodejs documentation. The only thing to mention here is that specs in JSON format are transformed into pretty-print multiline format (that is handy when original specification is JSON document containing long single line of code). Then, module updates project’s type definitions by applying swagger-to-flowtype CLI command to each of saved specifications.

So, running this command in console:

node ./typeUtils/generate-flow-types.js

will fetch all API specs and update related Flow types used in the project.

For convenience, the command is added into prepare npm script, so the module is executed on running npm install command. npm install is picked as developers run it to update project packages anyways. If you just want to update specifications without installing packages, run npm run update-flow-types . That’s how it is set up in package.json file:

"scripts": {
"update-flow-types": "node typeUtils/generate-flow-types.js",
. . . . .
"prepare": "npm run update-flow-types",
. . . . .
}

API specifications used in the project are listed in ./typeUtils/specsConfig.json file, that has the following structure:

{
"apiSpecs": [
{
"url": "https://petstore.swagger.io/v2/swagger.yaml",
"version": "",
"fileName": "PetStore"
},
{
// some another specification here
}
],

"example": { . . . . }
}

apiSpecs is array of specifications used in the project:

  • url — URL to download the specification;
  • version — optional argument; if presents, it is added to the end of url value with slash, like {url}/{version};
  • fileName — file name (without extension), that the downloaded specification will get when it is saved in ./apiSpecs directory. The file extension is picked based on Content-Type header of HTTP response: it will be .yaml for “application/yaml”, and .json for “application/json” respectively. For other values either missed Content-Type, .json will be used as a fallback. Also, this file name with .js extension will be assigned to Flow types file generated of the spec, when it is saved in ./types directory;

example is just an example of how SwaggerHub API specs should be defined, and is ignored normally.

After specification is saved in local directory, it is processed by “swagger-to-flowtype” npm package. “swagger-to-flowtype” is great open-source tool ( code can be found here https://github.com/yayoc/swagger-to-flowtype ), that performs all the magic of creating Flow types out of Swagger specification and saves it locally. The types definition is saved into ./types directory, as mentioned above. It should be added to VCS, to trace changes and to prevent re-running generating every time when you switch to different branch.

If apiSpecs field contains several API’s, they are processed asynchronouosly in parallel, to reduce the time that the procedure takes. Each of the API’s gets its own related Flow types file in ./types directory, they aren’t merged into one file.

Authorization on downloading API specifications

When publishing private API specifications in web, it is worth to protect them from unauthorized access. Simply sharing the links only with developers who should access them is not enough. There are different ways to accomplish this, and quite common is passing security token in “Authorization” HTTP header.

On SwaggerHub, this protection is enabled by default for private API’s, based on access rights of user’s account to particular API specification. To be able to download the specs, user needs to get personal secret token in his / her SwaggerHub account settings (Settings → API Keys), and then pass it in “Authorization” request header when downloading the API specification. Each user gets unique secret token.

If you host API specs on your own HTTP server, then simplest way is Basic Authentication. As of standard, user encodes his / her login:password (putting them in one string separated with colon) with Base64 encoding, prepends it with Basic prefix, and then passes the gotten string in “Authorization” header (so it acts as secret token). Whether you have single login & password for all your team, or each developer will get his / her own — this is up to you to decide what better fits your case.

And I didn’t try S3 neither GitHub Pages for hosting API specs, thus cannot describe their settings in details. So I leave investigating their documentation to you =) But anyways, these services have means to secure access to data you host there.

By design of our POC example project, access keys are stored in ./typeUtils/apiSecretKeys.json file. Please note: it must not be added to VCS, as it is supposed to contain user’s personal access keys (and even if entire your team uses same access token, anyways it must not appear in repository, this is good practice). The file has the following structure:

{
"apiSecretKeys": {
"https://api.swaggerhub.com/apis/kolobok86/Petstore": "some-auth-key-1234-567890-abcdefg",
"https://api.swaggerhub.com/apis/kolobok86/SomeOtherApiSpec": "some-another-auth-key-abcdef-1234567-890ghijk",
. . . . . . . . .
}
}

The keys are matched with API’s listed in ./typeUtils/specsConfig.json by API’s url field without version. So, changing API version doesn’t require updating of the secret key.

For instance, imagine we have API spec https://api.swaggerhub.com/apis/kilobok86/Petstore listed in ./typeUtils/specsConfig.json (in fact the spec doesn’t exist, this is just an example):

"apiSpecs": [
{
"url": "https://api.swaggerhub.com/apis/kolobok86/Petstore",
"version": "1.0.0",
"fileName": "PetStore"
}
]

So, we create related record in ./typeUtils/apiSecretKeys.json:

{
"apiSecretKeys": {
"https://api.swaggerhub.com/apis/kolobok86/Petstore": "some-secret-token-1234-567890-abcdefg"
}
}

If you don’t need to protect your API specifications (say, it is open-source project), or you use some publicly available specification like https://petstore.swagger.io/v2/swagger.yaml , just don’t list it in ./typeUtils/apiSecretKeys.json , and it will be fetched without authorization.

Example project repo contains file ./typeUtils/apiSecretKeys.json.example that can be renamed to “apiSecretKeys.json” and used as basis to fill with actual values.

Using the types in the project

Finally, when the type definitions are created, they can be used in the project. A very basic example of that usage is shown in ./src/index.js . As code with types needs to be transformed into bare javascript before running, I added “flow-remove-types” npm package to perform it, for sake of simplicity. You can use “babel” for that purpose, of course, especially since it has many other useful features in addition to processing type-enabled code. npm run build command performs the transformation and saves the prepared javascript in ./lib/index.js, and npm run start starts it.

Conclusion

And that’s it! Now, every time when your API changes, you simply run npm install or npm run update-flow-types to update type definitions in your project, and Flow shows if the change is about to break something. Though it doesn’t solve all the possible problems, it definitelly will prevent some nasty unobvious bugs.

Creds:
swagger-to-flowtype:
GitHub: https://github.com/yayoc/swagger-to-flowtype
NPM: https://www.npmjs.com/package/swagger-to-flowtype

Swagger / OpenAPI: https://swagger.io/specification/

--

--