loading...

Generating documentation on the fly in express

akshendra profile image Akshendra Pratap Singh ・4 min read

I little while ago, I decided to generate docs for some HTTP APIs I was about to build. The APIs were going to be used internally, so I didn't want to commit to any of the online solutions, like API Blueprint, Apiary or Swagger. And setting up something from scratch would have been, ... bit much. Also, I wanted the docs integrated within the code, avoiding any kind of context switch just to write documentation.

I was aware of jsdoc and esdoc, both of them allows us to write documentation in comments. However, their job is to document javascript code and not HTTP APIs. Then, I found a tool swagger-jsdoc, which generates swagger / OpenAPI specification from comments. This was just what I was looking for.

Let's see some code now

Just a simple server that lists animals and you can add your favorite animal too. Quite a novel concept.

const express = require('express');
const bodyparser = require('body-parser');

const app = express();
app.use(bodyparser.json({
  strict: false,
}));

const animals = [
  'panda', 'racoon', 'python',
];

app.get('/list', (req, res) => {
  return res.json(req.query.sort === 'yes' ? Array.from(animals).sort() : animals); // why is .sort inplace 😠
});

app.post('/add', (req, res) => {
  animals.push(...req.body.animals);
  return res.json({
    message: 'Added',
  });
});

app.listen(3000, () => {
  console.log('Server started at port 3000');
});

swagger-jsdoc requires comments to follow OpenAPI Specification, which is quite intuitive.

Adding documentation comments for /list route.

/**
 * @swagger
 * /list:
 *   get:
 *     summary: List all the animals
 *     description: Returns a list of all the animals, optionally sorted
 *     tags:
 *       - animals
 *     parameters:
 *       - in: query
 *         name: sort
 *         type: string
 *         required: false
 *         enum:
 *           - yes
 *           - no
 *     responses:
 *       200:
 *         description: List of animals
 *         schema:
 *           type: object
 *           properties:
 *             animals:
 *               type: array
 *               description: all the animals
 *               items:
 *                 type: string
 */

app.get('/list', (req, res) => {
  // ...
});

The first line is @swagger which helps swagger-jsdoc identify this comment block as swagger (OpenAPI) specification. Next few lines define the path, the method, a little summary, and description. tags are used to group the APIs.

The expected parameters, both query and path are described next. Our /list API expects an optional sort query parameter, which is used to decide whether the list of animals should be sorted or not, before sending.

Then we define the response. Status coming first, a little description and then the schema of the response. We are returning JSON here. However, it is easy to document other content types as well.

Same we will do for the /add request.

/**
 * @swagger
 * /add:
 *   post:
 *     summary: Add more animal
 *     description: Add animals to the list
 *     tags:
 *       - animals
  *     requestBody:
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               animals:
 *                 type: array
 *                 items:
 *                   type: string
 *     responses:
 *       200:
 *         description: Adds the animals in body
 *         schema:
 *           type: object
 *           properties:
 *             message:
 *               type: string
 *               default: 'Added'
 */
app.post('/add', (req, res) => {
  // ...
});

Now that we have the comments ready, we will hook up the swagger-jsdoc module.

// ... other modules
const swaggerJSDoc = require('swagger-jsdoc');

const app = express();
app.use(bodyparser.json({
  strict: false,
}));

const animals = [
  'panda', 'racoon', 'python',
];

// -- setup up swagger-jsdoc --
const swaggerDefinition = {
  info: {
    title: 'Animals',
    version: '1.0.0',
    description: 'All things animlas',
  },
  host: 'localhost:3000',
  basePath: '/',
};
const options = {
  swaggerDefinition,
  apis: [path.resolve(__dirname, 'server.js')],
};
const swaggerSpec = swaggerJSDoc(options);

// -- routes for docs and generated swagger spec --

app.get('/swagger.json', (req, res) => {
  res.setHeader('Content-Type', 'application/json');
  res.send(swaggerSpec);
});

// other routes

This will serve a swagger specification at /swagger.json. All that is left to do is render this spec in a more human-friendly way. I choose ReDoc for that. It has a simple setup.

Include an HTML file

<!DOCTYPE html>
<html>
  <head>
    <title>Quizizz Docs</title>
    <!-- needed for adaptive design -->
    <meta charset="utf-8"/>
    <link rel="shortcut icon" type="image/x-icon" href="https://quizizz.com/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">

    <!--
    ReDoc doesn't change outer page styles
    -->
    <style>
      body {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <!-- we provide is specification here -->
    <redoc spec-url='http://localhost:3000/swagger.json' expand-responses="all"></redoc>
    <script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
  </body>
</html>

We have set the http://localhost:3000/docs/swagger.json as the place to server JSON specification already. Let's set up a route to serve this HTML as well.

app.get('/docs', (req, res) => {
  res.sendFile(path.join(__dirname, 'redoc.html'));
});

The result,

ReDoc rendered documentation

There are more components to OpenAPI and swagger-jsdoc which make the process easier. You can write definitions for schemas/requests/responses that are used more than once and then use them in the docs. Check out Components Section | Swagger and Getting Started | Swagger JSDoc to define them in a JavaScripty way.

Code can be found here

Discussion

pic
Editor guide
Collapse
edbrannin profile image
Ed Brannin

Thanks for this article! I was hoping to use a jsdoc-to-Swagger parser, but I might need to give up and write the YAML.

The second-paragraph link to swagger-jsdoc is broken; it seems like a {% raw %}...{% endraw %} got escaped into the href.

Collapse
dharmax profile image
avi

Well guys, I left express and koa awhile ago and I use Hapi in all my projects now. It had organic support for documentation and validation for ages now and it is best designed all around.

Collapse
akshendra profile image
Akshendra Pratap Singh Author

I have used koa, but never tried hapi. Will check it out.

Collapse
dharmax profile image
avi

You do that...would love to hear what you think

Collapse
madeofhuman profile image
CJ Odina

Wow! Thanks! I'll check this out. I was literally just searching for an easy way of writing API documentation.

Collapse
electrode profile image
Electrode Cathode

Awesome, thanks for this Akshendra