Add Swagger to an Express.js app in five minutes

Add Swagger to an Express.js app in five minutes

We started the Express.js journey with a simple CRUD Express.js app that has a bunch of APIs. Though returned dummy data. Then we added MySQL support that made the app more usable. But still, one big piece is missing. And that’s API documentation in Swagger and Swagger UI. In this article, we go through how to add Swagger to an Express.js app in five minutes.

By contrast to Java Spring Boot that adding Swagger and Swagger UI is a piece of cake, thanks to huge sets of annotations. The process is quite different in Express.js and other languages like PHP. We need to handcraft the documentation, generate JSON or YAML file and then add it to the application.

Don’t panic! It is not difficult at all. We go through each step in this article.

Adding swagger-ui-express library

There are numerous libraries available for Swagger UI. Based on my experiments the best is <a href="https://www.npmjs.com/package/swagger-ui-express">swagger-ui-express</a>. It’s super easy to use. Hence, I highly recommend it.

To add swagger-ui-express to the project, just run,

$ npm install --save swagger-ui-express

Creating the Swagger documentation

By far this is the most timeconsuming step. We have to handcraft the documentation. For that, we leverage on Swagger Editor. We start by modifying the example and adding the stuff related to our User APIs.

There’s nothing much for me to explain here. It’s all trial and error. Just make sure to add the correct data type. It is not difficult at all. It’s just timeconsuming.

The final result looks like this:

openapi: 3.0.1
info:
  title: User API
  description: 'This is a sample User API created with Express.js. You can find more about it [here](https://github.com/kasramp/sample-rest-with-expressjs).'
  contact:
    email: [email protected]
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
  version: 1.0.0
tags:
- name: user
  description: Operations about user
paths:
  /api/v1/users:
    get:
      tags:
      - user
      summary: Get list of users
      operationId: getUsers
      responses:
        200:
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
                type: string
    post:
      tags:
      - user
      summary: Create a user
      operationId: createUser
      requestBody:
        description: Created user object
        content:
          'application/json':
            schema:
              $ref: '#/components/schemas/UserDto'
        required: true
      responses:
        201:
          description: User created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        400:
          description: Invalid payload supplied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/users/{id}:
    get:
      tags:
      - user
      summary: Get a user by id
      operationId: getUserById
      parameters:
      - name: id
        in: path
        description: 'The id of a user'
        required: true
        schema:
          type: integer
      responses:
        200:
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        400:
          description: Invalid id supplied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    put:
      tags:
      - user
      summary: Update a user
      operationId: updateUser
      parameters:
      - name: id
        in: path
        description: id of a user to update
        required: true
        schema:
          type: integer
      requestBody:
        description: Updated user object
        content:
          'application/json':
            schema:
              $ref: '#/components/schemas/UserDto'
        required: true
      responses:
        200:
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        400:
          description: Invalid payload supplied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      tags:
      - user
      summary: Delete a user
      operationId: deleteUserById
      parameters:
      - name: id
        in: path
        description: The id of user to be deleted
        required: true
        schema:
          type: integer
      responses:
        204:
          description: User deleted successfully
        400:
          description: Invalid id supplied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    UserDto:
      type: object
      properties:
        firstName:
          type: string
        lastName:
          type: string
        age:
          type: integer
          format: int64
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
        firstName:
          type: string
        lastName:
          type: string
        age:
          type: integer
          format: int64
    Error:
      type: object
      properties:
        error: 
          type: string
    ApiResponse:
      type: object
      properties:
        code:
          type: integer
          format: int32
        type:
          type: string
        message:
          type: string

But we need the json version. We can generate that via Swagger Editor too. Just go to File and click on Convert and save as JSON.

{
  "openapi": "3.0.1",
  "info": {
    "title": "User API",
    "description": "This is a sample User API created with Express.js. You can find more about it [here](https://github.com/kasramp/sample-rest-with-expressjs).",
    "contact": {
      "email": "[email protected]"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    },
    "version": "1.0.0"
  },
  "tags": [
    {
      "name": "user",
      "description": "Operations about user"
    }
  ],
  "paths": {
    "/api/v1/users": {
      "get": {
        "tags": [
          "user"
        ],
        "summary": "Get list of users",
        "operationId": "getUsers",
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User",
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "user"
        ],
        "summary": "Create a user",
        "operationId": "createUser",
        "requestBody": {
          "description": "Created user object",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "User created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "400": {
            "description": "Invalid payload supplied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users/{id}": {
      "get": {
        "tags": [
          "user"
        ],
        "summary": "Get a user by id",
        "operationId": "getUserById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The id of a user",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id supplied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "user"
        ],
        "summary": "Update a user",
        "operationId": "updateUser",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "id of a user to update",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "description": "Updated user object",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserDto"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful operation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "400": {
            "description": "Invalid payload supplied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "user"
        ],
        "summary": "Delete a user",
        "operationId": "deleteUserById",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "The id of user to be deleted",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "User deleted successfully"
          },
          "400": {
            "description": "Invalid id supplied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "UserDto": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "age": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "age": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "ApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32"
          },
          "type": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      }
    }
  }
}

Now let’s create a directory called swagger in the application root and place both YAML and JSON versions.

Use swagger.json in the project

Now that we have the documentation ready, we just need to glue things together. Head to app.js and add the following lines of code,

import swaggerUi from "swagger-ui-express";
import swaggerDocument from "./swagger/swagger.json";
// removed for brevity
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));

As you can see we just added only three lines of code. The first line imports the swagger-ui-express library. The second line points to the Swagger documentation that we created in the previous step. And the last line creates a route to serve the document.

Now if you run, npm run start and type http://localhost:8090/api-docs you should be able to see the Swagger UI and interact with the APIs,

Add Swagger to an Express.js app in five minutes
Swagger UI

The complete example is available on GitHub at the link below,
https://github.com/kasramp/sample-rest-with-expressjs

That’s all. Hope it didn’t take more than five minutes to add Swagger to the Express.js 😀

Inline/featured images credits