How to Document an Express API with Swagger UI and JSDoc
Tags: javascript, node, api, docs
Published: 2020-11-29 19:01:33


[JSDoc](https://jsdoc.app/) is a popular tool for generating documentation from comments in the source code of your app. This serves two purposes. First, the documentation is directly available to anyone viewing the source code. Second, the comments can be compiled later into a complete set of reference documentation.

[Swagger](https://swagger.io) provides a tool for presenting this documentation: [Swagger UI](https://swagger.io/tools/swagger-ui/). Swagger UI creates a web page from [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md) definitions. As this tutorial will show, these definitions can be written in YAML directly in JSDoc comments.

In this tutorial, you will set up a [Swagger UI](https://swagger.io/tools/swagger-ui/) documentation web page for an Express API. You can then write JSDoc comments in your API's source code to generate the OpenAPI definitions. By the end, you will have documentation that follows the OpenAPI Specification, presented from a custom `/docs` endpoint added to your Express API:

![Swagger UI Docs Page](https://dev-to-uploads.s3.amazonaws.com/i/juc1ws15olef5vm7peih.png)

## Prerequisites

To complete this tutorial, you'll need

* Familiarity with REST APIs and [Express](https://expressjs.com/)

* [Node.js](https://nodejs.dev/learn/how-to-install-nodejs) installed on your system

* An **Express-based REST API** running on a local Express server. If you don't have one, you can install the [Express API](https://github.com/kabartolo/jsonplaceholder-express-api) used in this tutorial. It retrieves user data from [JSONPlaceholder](https://jsonplaceholder.typicode.com/).

To install and run the sample Express API, first clone the repository (replace `test-api` with the directory name of your choosing):

```sh
git clone https://github.com/kabartolo/jsonplaceholder-express-api test-api
```

Next, run the following commands to start the Express server (replace `test-api` with the name of the directory you just created):

```sh
cd test-api
npm install
npm run start
```

**Navigate** to [`localhost:3000`](http://localhost:3000) to see the API. You should see links to `/users` and `/users/1`. 

![API Homepage](https://dev-to-uploads.s3.amazonaws.com/i/nlqr49528h817w6wd4iv.png)

Navigate to either of these to see user data from JSONPlaceholder.

The code added during this tutorial can be found in the repository's [`docs` branch](https://github.com/kabartolo/jsonplaceholder-express-api/tree/docs).

## Terminology
*OpenAPI* is the name of the specification, while *Swagger* is the set of tools that implement this specification. See [What Is the Difference Between Swagger and OpenAPI?](https://swagger.io/blog/api-strategy/difference-between-swagger-and-openapi/)

This tutorial uses the following API-related terms and definitions defined by [OpenAPI](https://swagger.io/docs/specification/api-host-and-base-path/):

```
    https://api.example.com/v1/users?role=admin&status=active
    \________________________/\____/ \______________________/
             server URL       endpoint    query parameters
                                path
```                              

* **Server URL or base URL**: The base URL for all API endpoints: `localhost:3000` or `example.com/api`
* **Endpoint path**: The path representing the location of the resource (relative to the base URL): `/users` or `/users/1`
* **Operation**: The HTTP method used to manipulate endpoint paths: GET, POST, PUT, DELETE
* **Resource**: Information representing a real-world object (e.g., a user or book), usually returned by the API as JSON data. Represented by a database **model** in Express.

The full URL used to retrieve data from the API is formed by adding the endpoint to the base URL: `localhost:3000/users`.

## Step 1: Set up the application

### 1.1: Install `swagger-jsdoc` and `swagger-ui-express`

To create a Swagger UI page from JSDoc comments, you’ll need a way to pass your documentation to Swagger UI:

*  [`swagger-jsdoc`](https://github.com/Surnet/swagger-jsdoc)  generates OpenAPI definitions from JSDoc comments.
*  [`swagger-ui-express`](https://github.com/scottie1984/swagger-ui-express) creates the Swagger UI page from these definitions.

To install `swagger-jsdoc` and `swagger-ui-express` to your Express API, run

```sh
npm install swagger-jsdoc@5.0.1 --save-exact
npm install swagger-ui-express --save
```

> This tutorial uses `swagger-jsdoc` version `5.0.1`. The latest version might not be compatible with this tutorial. 

### 1.2: Create an API specification

Swagger UI creates a docs page from a set of OpenAPI definitions. These definitions are written in [YAML](https://yaml.org/) or [JSON](https://json.org) to describe a REST API. For more information on the basic structure of the OpenAPI Specification, see [Basic Structure](https://swagger.io/docs/specification/basic-structure/).

In your Express API's `app.js` file, add the following code below the list of required modules:

```js
// app.js
const swaggerJSDoc = require('swagger-jsdoc');

const swaggerDefinition = {
  openapi: '3.0.0',
  info: {
    title: 'Express API for JSONPlaceholder',
    version: '1.0.0',
  },
};

const options = {
  swaggerDefinition,
  // Paths to files containing OpenAPI definitions
  apis: ['./routes/*.js'],
};

const swaggerSpec = swaggerJSDoc(options);
```

The `swaggerDefinition` object (i.e., the OpenAPI definition) defines the root information for your API. Provide a few basic pieces of information to the `swaggerDefinition`, such as the `title` and `version` of your API; you can fill in more later.

The `options` object contains this `swaggerDefinition` object and an array of paths called `apis`. These are paths to files containing other OpenAPI definitions. These file paths should be relative to the root directory of your Express API. In our case, definitions will be written in JSDoc directly in the `/routes` files. You can list the filenames individually or use the wildcard delimiter `*` to add all JavaScript files in a directory, as shown above.

The `options` object is used by `swagger-jsdoc` to produce an OpenAPI specification in a variable called `swaggerSpec`. This specification is equivalent to the `swagger.json` or `swagger.yaml` file normally used by Swagger UI to create a docs page. You'll pass this object to Swagger UI in the next step.

**Restart** the Express server to ensure there are no errors. If you get any errors at this stage, check that your `swagger-jsdoc` version is `5.0.1` exactly.

### 1.3: Create the Swagger UI docs page

To create a Swagger UI page for your Express API, include `swagger-ui-express` in the `app.js` file. Then, add an endpoint path called `/docs` (or any name of your choosing):

```js
// app.js
// ...
const swaggerJSDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

// ...

var app = express();

app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
```

As shown above, `swagger-ui-express` provides two callbacks to set up the endpoint: one to **set up** Swagger UI with the `swaggerSpec` definitions and one to **serve** it to the `/docs` endpoint.

**Restart** the Express server, and **navigate** to [`localhost:3000/docs`](http://localhost:3000/docs) in the browser.

You'll see the title and version number of your Express API, as well as the OpenAPI version number (`3.0.0`). Since we have no other definitions yet, you'll see a "No operations defined in spec!" message:

![Basic Swagger UI Page](https://dev-to-uploads.s3.amazonaws.com/i/fvsnzhvcj94o3ced81lk.png)

You now have the start of a beautiful docs page for your API! The rest of this tutorial provides a basic introduction to OpenAPI definitions.

## Step 2: Define your API's root information

You've created a Swagger UI docs page, and you're set to start writing docs. But first, you should add more root definitions for the API.

Return to `app.js`. Note that the `info` object maps to OpenAPI's [Info Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#infoObject) to define a title, description, list of servers, contact information, and list of paths for your API.

Here is an example of a more complete definition:

```js
// app.js
const swaggerDefinition = {
  openapi: '3.0.0',
  info: {
    title: 'Express API for JSONPlaceholder',
    version: '1.0.0',
    description:
      'This is a REST API application made with Express. It retrieves data from JSONPlaceholder.',
    license: {
      name: 'Licensed Under MIT',
      url: 'https://spdx.org/licenses/MIT.html',
    },
    contact: {
      name: 'JSONPlaceholder',
      url: 'https://jsonplaceholder.typicode.com',
    },
  },
  servers: [
    {
      url: 'http://localhost:3000',
      description: 'Development server',
    },
  ],
};
```

If you have a production server, add the URL and a description to the `servers` list. See [Basic Structure](https://swagger.io/docs/specification/basic-structure/) for more information on the other properties you can add to the root definition.

In the [OpenAPI docs](https://swagger.io/docs/specification/basic-structure/), you'll notice there's also a `paths` field. You won't need to specify the path definitions here, since each path is defined separately in a JSDoc comment (to be added in the next step). These path definitions are compiled by `swagger-jsdoc` into a `paths` object for you.

**Restart** the Express server, and **navigate** again to [`localhost:3000/docs`](http://localhost:3000/docs) in the browser. You should see more information about your API at the top of the docs page:

![Swagger UI Page with API Information](https://dev-to-uploads.s3.amazonaws.com/i/9lw2xrss7nz5f0fq1t5i.png)

You can now start documenting your Express routes.

## Step 3: Write the docs

With a Swagger UI docs page available at the `/docs` endpoint and a complete set of root information on your API, you can start writing your path definitions. Each path definition corresponds to an Express route in your API. It describes both the operation and endpoint path, such as `GET /users` and `DELETE /users/:id`.

### 3.1: Document the routes

To document `/routes/users.js`, first add a comment starting with `@swagger` above the first route. Follow this with some basic information about the route:

```js
// routes/users.js

/**
 * @swagger
 * /users:
 *   get:
 *     summary: Retrieve a list of JSONPlaceholder users
 *     description: Retrieve a list of users from JSONPlaceholder. Can be used to populate a list of fake users when prototyping or testing an API.
*/
router.get('/', function(req, res) {
  //...
});
```

Note that `swagger-jsdoc` looks for comments with a `@swagger` or `@openapi` tag to create OpenAPI definitions.

As shown in the code example, add the endpoint path `/users` and the operation `get` (indented two spaces). The path in the Express router function `get('/')` is relative to `/users`, so the path in the definition should be `/users`.

The `summary` should be a brief description of the goal of this route. The `description` should provide more detail, such as when or why you would want to use the route.

> Be sure to use two spaces (or four spaces) for indentation, not tabs. Refer to [YAML Syntax](https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html) for more information.

**Restart** the Express server, and **navigate** again to [`localhost:3000/docs`](http://localhost:3000/docs) in the browser. You should see a listing for `GET /users` near the bottom of the page:

![Swagger UI Page Showing First Path](https://dev-to-uploads.s3.amazonaws.com/i/76tz1fkiserr8dnwe02c.png)

### 3.2: Document responses

Your users will want to know what is returned when this GET request is successful (i.e., with a status code of `200`).  To define a successful response, add a `responses` object and a response called `200` to the path definition:

```js
// routes/users.js

/**
 * @swagger
 * /users:
 *   get:
 *     summary: Retrieve a list of JSONPlaceholder users.
 *     description: Retrieve a list of users from JSONPlaceholder. Can be used to populate a list of fake users when prototyping or testing an API.
 *     responses:
 *       200:
 *         description: A list of users.
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 data:
 *                   type: array
 *                   items:
 *                     type: object
 *                     properties:
 *                       id:
 *                         type: integer
 *                         description: The user ID.
 *                         example: 0
 *                       name:
 *                         type: string
 *                         description: The user's name.
 *                         example: Leanne Graham
 */
router.get('/', function(req, res) {
  //...
});
```

The `description` field describes the response or what it returns. The `content` field describes the content type (`application/json`), and the `schema` describes the response object. In our case, JSONPlaceholder returns an object with a `data` field, which contains the data you've requested. For this response, `data` contains an array of user objects. Add just one or two user properties (e.g., `id` and `name`) to avoid cluttering the file for now. 

Add a real example value for each property (e.g., `'Leanne Graham'`); otherwise, Swagger UI creates a generic example such as `'string'`.

> Notice how the types are defined in this schema. For example, to define an array, add `type: array` and an `items` field. Read more about types in the [Data Types](https://swagger.io/docs/specification/data-models/data-types/) documentation.

You can also describe **error** responses this way. See Swagger's [Describing Responses](https://swagger.io/docs/specification/describing-responses/) documentation for more details on the fields available for describing each response.

**Restart** the Express server, and **navigate** again to [`localhost:3000/docs`](http://localhost:3000/docs) in the browser. You should see the response, an example value (using the example values you provided for each property), and the schema for the data returned in this response:

![Response and Schema](https://dev-to-uploads.s3.amazonaws.com/i/pyqmavht837zspni1mrx.png)


Next, define the `GET /users/:id` path by adding the fields we've covered already (`summary`, `description`, and `responses`):

```js
// routes/users.js

 /**
 * @swagger
 * /users/{id}:
 *   get:
 *     summary: Retrieve a single JSONPlaceholder user.
 *     description: Retrieve a single JSONPlaceholder user. Can be used to populate a user profile when prototyping or testing an API.
 *     responses:
 *       200:
 *         description: A single user.
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 data:
 *                   type: object
 *                   properties:
 *                     id:
 *                       type: integer
 *                       description: The user ID.
 *                       example: 0
 *                     name:
 *                       type: string
 *                       description: The user's name.
 *                       example: Leanne Graham
*/

router.get('/:id', function(req, res) {
  //...
});
```

Here, the path parameter (`id`) is added to the endpoint path: `/users/{id}`. Curly brackets (`{}`) are used to mark a path parameter in the endpoint path. Note that the colon style (`/users/:id`) won't work with Swagger (thanks @sherwinwater for pointing this out!).

The `data` object in this `schema` contains a single user object instead of an array of user objects, but the properties are the same. 

Next, define `POST /users` by adding the fields we've covered already (`summary`, `description`, and `responses`):

```js
// routes/users.js

/**
 * @swagger
 * /users:
 *   post:
 *     summary: Create a JSONPlaceholder user.
 *     responses:
 *       201:
 *         description: Created
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 data:
 *                   type: object
 *                   properties:
 *                     id:
 *                       type: integer
 *                       description: The user ID.
 *                       example: 0
 *                     name:
 *                       type: string
 *                       description: The user's name.
 *                       example: Leanne Graham
*/
router.post('/', function(req, res) {
// ...
});
```
A successful response in this case would be `201`. It returns an object with a `data` field containing the new user. 

You can continue adding path definitions for the remaining routes in the same way. We'll do some refactoring in a later step.

**Restart** the Express server, and **navigate** again to [`localhost:3000/docs`](http://localhost:3000/docs) in the browser. You'll now see a listing for `GET /users/{id}`, `POST /users`, and any other path definitions you've added:

![Swagger UI Docs Page](https://dev-to-uploads.s3.amazonaws.com/i/juc1ws15olef5vm7peih.png)

### 3.3: Document the requests

Request data such as parameters and request bodies can also be documented in your OpenAPI definitions. For example, `GET /users/:id` has an `id` parameter, which should be documented.

To document parameters, add a `parameters` field to the path definition:

```js
// routes/users.js

/**
 * @swagger
 * /users/{id}:
 *   get:
 *     summary: Retrieve a single JSONPlaceholder user.
 *     description: Retrieve a single JSONPlaceholder user. Can be used to populate a user profile when prototyping or testing an API.
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         description: Numeric ID of the user to retrieve.
 *         schema:
 *           type: integer
 *     responses:
 *       200:
 *         ...
 */
router.get('/:id', function(req, res) {
  //...
});

```

In the definition for this parameter, `in` defines the parameter's location (in this case, it's a path parameter because it's part of the path). You can also add a `name`, `description`, and `schema` and whether the parameter is `required`. See [Describing Parameters](https://swagger.io/docs/specification/describing-parameters/) for more details. 

**Restart** the Express server, and **navigate** again to [`localhost:3000/docs`](http://localhost:3000/docs) in the browser. You'll see a list of parameters for this route:

![Route Parameters](https://dev-to-uploads.s3.amazonaws.com/i/j9keumw9pxe95tzq2kpo.png)



Next, document the **request body** for `POST /users` to describe the data required to create a new user in the database. To do this, **add** a `requestBody` field to this path definition:

```js
// routes/users.js

/**
 * @swagger
 * /users:
 *   post:
 *     summary: Create a JSONPlaceholder user.
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               name:
 *                 type: string
 *                 description: The user's name.
 *                 example: Leanne Graham
 *     responses:
 *       201:
 *         ...
*/
router.post('/', function(req, res) {
// ...
});
```

This adds a request body schema to this path definition. This example shows that `name` can be sent in the request body. You can add more properties for new users later. See [Describing Request Body](https://swagger.io/docs/specification/describing-request-body/) for more details.

**Restart** the Express server, and **navigate** again to [`localhost:3000/docs`](http://localhost:3000/docs) in the browser. You'll see a section called `Request body` with the schema you've provided:

![Request Body](https://dev-to-uploads.s3.amazonaws.com/i/5b7ktwvrhuqlcugl7q14.png)

### 3.4: Document the resources

You might have noticed you've repeated the user schema several times in the documentation so far. To avoid this duplication, you can define the user schema in one place and reference it from elsewhere.

Each model defined by your Express API can be documented separately as a schema definition (or [component](https://swagger.io/docs/specification/components/)). To do this for the user model, add a `User` schema definition to the top of the file, under `components/schemas`:

```js
// routes/users.js

/**
 * @swagger
 * components:
 *   schemas:
 *     User:
 *       type: object
 *       properties:
 *         id:
 *           type: integer
 *           description: The user ID.
 *           example: 0
 *         name:
 *           type: string
 *           description: The user's name.
 *           example: Leanne Graham
 */
```

You can then reference this schema definition using `$ref`:

```js
// routes/users.js

/**
 * @swagger
 * /users:
 *   get:
 *     summary: Retrieve a list of JSONPlaceholder users
 *     description: Retrieve a list of users from JSONPlaceholder. Can be used to populate a list of fake users when prototyping or testing an API.
 *     responses:
 *       200:
 *         description: A list of users.
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/User'
 */
router.get('/', function(req, res) {
  //...
});
```
The `$ref` path uses [JSON Reference](https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03) notation. The `#` symbol indicates the root of the current document, and the remaining nested values are then resolved in order. For more information, see [Using $ref](https://swagger.io/docs/specification/using-ref/).

**Restart** the Express server, and **navigate** again to [`localhost:3000/docs`](http://localhost:3000/docs) in the browser. Your path definitions will now use this `User` schema, and you should see a schema definition for `User` at the bottom of the page:

![User Schema](https://dev-to-uploads.s3.amazonaws.com/i/7cdcc3ad8wzz57aufx97.png)

Similarly, you can define a `NewUser` object to reference in the `POST /users` request body. Since it contains some but not all of the fields from the `User` schema, you can also use `$ref` to avoid duplication between them:

```js
/**
 * @swagger
 * components:
 *   schemas:
 *     NewUser:
 *       type: object
 *       properties:
 *         name:
 *           type: string
 *           description: The user's name.
 *           example: Leanne Graham
 *     User:
 *       allOf:
 *         - type: object
 *           properties:
 *             id:
 *               type: integer
 *               description: The user ID.
 *               example: 0
 *         - $ref: '#/components/schemas/NewUser'
 */
```

The `allOf` keyword combines model definitions, in this case the `NewUser` definition (containing the `name` property) and an object with an `id` property. See [oneOf, anyOf, allOf, not](https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/) for more details.

You can now reference `NewUser` from the request body definition for `POST /users`:

```js
/**
 * @swagger
 * /users:
 *   post:
 *     summary: Create a JSONPlaceholder user.
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/NewUser'
 *     responses:
 *       201:
 *         ...
*/
router.post('/', function(req, res) {
  // ...
});
```

**Restart** the Express server, and **navigate** again to [`localhost:3000/docs`](http://localhost:3000/docs) in the browser. You'll see your `NewUser` schema in the request body definition for `POST /users`:

![NewUser Schema](https://dev-to-uploads.s3.amazonaws.com/i/glkw439i08pi4bk00bak.png)
This covers the basic techniques for producing OpenAPI definitions in JSDoc comments.

## Conclusion

You are now set up to produce a complete reference documentation page for your Express API. You've created a basic set of OpenAPI definitions and a Swagger UI page that displays them. If you want more practice with the OpenAPI Specification, you can finish documenting the `jsonplaceholder-express-api`.

This tutorial has also covered the basics of writing OpenAPI definitions. To complete your documentation, consult the [OpenAPI Specification](https://swagger.io/specification/) and the [Swagger Docs](https://swagger.io/docs/specification/about/).

To see a version of the `jsonplaceholder-express-api` that includes all the code added during this tutorial, see the repository's [`docs` branch](https://github.com/kabartolo/jsonplaceholder-express-api/tree/docs).