Maintaining REST API Documentation with Node.js — Part I

Nelson Gomes
Pipedrive R&D Blog
Published in
6 min readMar 11, 2021
Image taken from kinsta.com

Creating and maintaining APIs is no easy task — we create methods, test them, and then update them several times. Over this span of time, it’s easy to forget to update the documentation which thus makes it stale and irrelevant.

Also, many APIs are composed by an assortment of different services that have their own definition, methods, and paths. All of this combined makes the complexity of keeping a full set of documentation up-to-date extremely complex and difficult.

In this two-part series, we plan to detail how to maintain your API documentation and keep it up-to-date in Node. We will also introduce a new component recently created to assist you in this task. Throughout we’ll be using OpenAPI specification to maintain documentation in an easy way.

You can find part II of this article here: https://medium.com/pipedrive-engineering/maintaining-rest-api-documentation-with-node-js-part-ii-26d1a622d3fe

To achieve our goals we’ll use swagger-ui-express as a UI client and ts-openapi, a newly created Node component that allows us to declare our API schemas in Node.

As you will likely notice when getting into the code, the schemas are based on Joi types. Types.* are just wrappers to assist you, which means you can create a schema for your request parameters and use it in a validation middleware to keep your security in sync with your APIs with only minimal changes. To ensure these schemas don’t waste your time, they can also be used to validate and document your APIs.

Developers tend to use Joi to validate data schemas in node, but Joi is more powerful than just that. This makes it a good match to use Joi to describe an API and also to harden it, combining both worlds — specially because there is no good options out there for this purpose. This allows us to keep our APIs documented and secure.

This article (and the next) will assume you have at least some knowledge in Node and Typescript (which the examples are written in).

Part I:

  • Creating and setting up your REST server
  • Giving a UI to APIs
  • Advanced examples
  • Applying security schemes

Part II:

  • Creating a validation middleware to make your APIs rock solid
  • Automating documentation generation
  • Fetching different service schemas
  • Combining multiple OpenAPI schemas to keep your full API spec up to date

We hope we caught your attention by this point, so, let’s get started!

Step1: Create and setup your REST server

Let’s create a new project:

mkdir server
cd server
npm init
npm install --save typescript express
npm install --save-dev @types/express
npx tsc --init
mkdir src
touch src/server.ts

Update the package.json file by adding ‘tsc’ in scripts tag to call typescript functions from the command line:

"scripts": {
...,
"build": "tsc"
}

Update the tsconfig.json file by adding ‘include’ to the file end:

  },
"include": ["src/**.ts"]
}

At this point you should be able to build your project, now edit src/server.ts and change it to:

Now let’s build it and run it:

npm run build
node src/server.js

Check the browser on URL: http://localhost:8000

How your server should look like.
How your server should look like.

This is a very basic server configuration, but it’s enough for demo purposes. If you need help building your awesome server, there’s luckily plenty of tutorials available online.

Step 2: Giving a UI to APIs

Now let’s add our UI to allow us to call out methods from OpenAPI specification produced by ts-openapi:

npm install --save swagger-ui-express ts-openapi
npm install --save-dev @types/swagger-ui-express

Next, create a separate src/hello.ts file:

and a src/openapi.ts file:

Finally, let’s change our src/server.ts file content:

At this point, we have the following endpoints available:

  • GET / hello-world method
  • GET /api-docs/ our UI
  • GET /openapi.json our JSON OpenAPI schema

Now let’s rebuild, restart and open the browser to see the look and feel of it:

Testing our API for the first time.

Some notes at this point:

  • As you can see in the code, we don’t need to export OpenApi schema in JSON for the UI to work, but we will need it to be accessible for the second part of this article.
  • Also, ts-openapi is only used during the server initialization phase, when we declare APIs, this means it does not cause any performance impact after initialization is complete.
  • With this setup, we don’t need external tools to call our API, like cURL, Postman or Insomnia, it’s self-contained and automatically updated when code changes and it’s documentation is always up-to-date.
  • visible parameter in addPath method is useful when you want to hide an operation from your API declaration.
  • Lastly, tags allows your methods to be grouped in categories. In this case, grouped by ‘Dummy APIs’, but you can put any operation into the categories that you wish, just add different tags.

Step 3: Advanced examples

Now that we are up and running, let’s create some more complex examples:

  • GET /customer/:id to get a specific customer, returns customer object
Example on how to use path parameters.

If you look at the JSON schema output you’ll notice that the path “/customer/:id” was automatically converted to OpenAPI format: “/customer/{id}”.

At this point we’re beginning to have too much code to follow, but don’t worry, sources will be available so let’s work with some query parameters:

  • GET /customer/list to get customer list and returns a dummy customer array, we can send an optional page number and the records per page we can also filter by customer types.
Doing a GET request with query parameters.

If you watched the video, the URL called was http://localhost:8000/customer/list?page=0&records=10&types=platinum&types=silver because we used an array of string enums.

  • POST /customer creates a customer, and has an optional header to pass the original request id, for traceability, expects a body and returns created customer data:
Create a customer, POST demo.

Step 4: Security Schemes

We should already be capable of making GETs, POSTs, passing data in params, query, body and header fields, and more. You can probably figure out the rest of the methods and details already by yourself, so let’s instead get to the more juicy stuff — security. Since no quality API works without security & the OpenAPI spec (https://swagger.io/docs/specification/authentication/) defines a lot of security schemes that you can use and we implemented them all, providing these methods:

  • basicAuth — base64 encoded user/password in an Authorization header
  • apiKeyAuth — security by providing a key either in header, query or cookie
  • bearerAuth Authorization header with a Bearer <token>
  • cookieAuth — authentication with cookies
  • oauth2ImplicitAuth — OAuth 2.0 fetching a token
  • oauth2AuthorizationCodeAuth — OAuth 2.0 with authorization code
  • oauth2PasswordAuth — OAuth 2.0 with a user/password login
  • oauth2ClientCredentialsAuth- OAuth 2.0 in server-to-server authorization scenarios

OpenAPI allows you to declare security schemes that you can use either globally (forcing all methods to be authenticated) or locally (to specific methods). Using bearerAuth as an example, we can do this via 2 paths, globally:

// declare security schemes available, each with an ID
openApi.declareSecurityScheme("bearerSecurity", bearerAuth());
// declare global schemes (applicable to all methods)
openApi.addGlobalSecurityScheme("bearerSecurity");

or just by cherry-picking and applying the security schemes to the methods you want:

Let’s change this to see how our demo API behaves when we declare a global security scheme to it and apply it to customer creation:

Demo of using a security scheme in your API

Final notes

  • Since this is the first release of ts-openapi, please bear with us and note that in these examples some code was under-optimized. Also, the server was dead simple, but it demonstrated the most important thing, how to use it. Note that ts-openapi does not implement the full OpenAPI spec and it still lacks things like classes, but we’ll get there.
  • If you use things such as ‘Service Discovery’ providers, you will probably need to update your servers when you send the JSON so that your servers list is up to date.
  • Feel free to provide feedback to improve this project. Even though there are newer technologies like GraphQL, REST APIs are still widely used given their ease to use.

Part II of this article focus not on generating a service documentation but on combining multiple service definitions into a single centralized one: https://medium.com/p/26d1a622d3fe

Useful links:

Repository for this code can be found at https://github.com/nelsongomes/server and you can find detailed info about OpenApi specification, UI interface and schema generator in these links:

Interested in working in Pipedrive?

We’re currently hiring for several different positions in several different countries/cities.

Take a look and see if something suits you

Positions include:

— Software Engineer in DevOps
— Site Reliability Engineer
— Junior Infrastructure Engineer
— Database Engineer
— And several more

--

--

Nelson Gomes
Pipedrive R&D Blog

Works in Pipedrive as senior SRE, has a Degree in Informatics from University of Lisbon and a Post-grad in project management.