In this article we will learn how we can auto generate OpenAPI Specifications using Swagger for RESTful APIs which helps us to describe, consume and visualize the APIs. With the help of swagger we will learn to autogenerate API documentation for REST APIs we built for CRUD operations.

Checkout full source code for this implementation here.

What is OpenAPI?

Previously known as the swagger specification, the OpenAPI, is a specification for machine-readable interface files for producing, describing, consuming and visualizing RESTful web services with the help a OpenAPI definition which typically is a YAML or JSON file. In simple words, OpenAPI describes what a specific API or Service does.

What is Swagger?

Swagger is suite of open source and professional tools which helps developers to define the openAPI specifications discussed above for RESTful APIs .

Prerequisites

Before we start make sure to have the following things installed

  • Visual studio code
  • NodeJS
  • ExpressJS
  • MongoDB
  • Mongoose
  • Code we implemented in this article, as we will be documenting the same APIs with swagger.

Setting up Swagger in NodeJS Express app

To initialize swagger in a express app we will be making use of the following packages

  • swagger-jsdoc – Generates swagger doc based on JSDoc
  • swagger-ui-express – helps in serving a swagger UI with the help of JSDoc

Install these packages by running the following command

npm install swagger-jsdoc swagger-ui-express

Add the following code in app.js file to initilaize swagger

const mongoose = require('mongoose');
require('./models/users.model')
const express = require('express');
require('dotenv').config();
const port = process.env.PORT || "8000";
const usersRoute = require('./routes/index.js');
const cors =  require('cors');
const swaggerUI = require('swagger-ui-express')
const swaggerJSDoc = require('swagger-jsdoc')

const app = express();

const options = {
	definition: {
		openapi: "3.0.0",
		info: {
			title: "Mongo CRUD APIs",
			version: "1.0.0",
			description: "Mongo CRUD APIs",
		},
		servers: [
			{
				url: "http://localhost:8000",
			},
		],
	},
	apis: ["./routes/*.js"],
};

const specs = swaggerJSDoc(options);

app.use("/api-docs", swaggerUI.serve, swaggerUI.setup(specs));

app.use(cors())
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

const dbURI = process.env.DB_URI;

mongoose
	.connect(dbURI, {
		useNewUrlParser: true,
		useUnifiedTopology: true,
	})
	.then(() => console.log("Database Connected"))
	.catch((err) => console.log(err));

mongoose.Promise = global.Promise;

app.use('/', usersRoute);

module.exports = app;

app.listen(port, () => {
    console.log(`Listening to requests on http://localhost:${port}`);
  });

To initialize swagger we first imported swagger-jsdoc and swagger-ui-express into the main app.js file. Then we define options for swagger-JSDoc where we give all the basic information like openAPI version, description for our API suite, servers on which APIs are hosted, and file routes where we have defined the REST APIs, which in our case will be the routes.js file in routes folder. Now we use use this options object to define swaggerJSDoc specification. And at last, we create a endpoint for swaggerUI to serve the API documentation for visualization using the swaggerJSDoc specification.

Let’s run the app and check if the application is running. You should see the following messages in terminal.

nodemon App

Using swagger to Autogenerate REST APIs documentation

We can generate swagger API documentation by simply adding a multi-line comment containing API details. Let’s see how exactly it is done.

Using swagger for GET API documentation

Let’s take a API we created for read operation and use swagger to document it.

//Reading multiple records
/**
 * @swagger
 * /read:
 *   get:
 *     summary: Returns the list of all users
 *     responses:
 *       200:
 *         description: the list of all users
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *       500:
 *         description: internal error
 */
router.get('/read', async (req,res)=>{
    try{
        const result = await User.find();
        return res.send(result)
    }catch(err){
        res.status(500).send(err)
    }
})

Here we have defined swagger description using a multiline comment above the REST API we created for a basic read operation.

First we give the api path, then the type of request, which is GET in this case, then a short summary about the API. Then we defined the response which we will get back from the API. Here we have documented two status codes and the type of content to expect from API response.

This completes swagger documentation for this read operation, let’s check the result in browser, by navigating to localhost:8000/api-docs/.

swagger read

Here we can see that we have a decent looking UI with our server URL and read API. There is a option to expand the API, lets expand and see what it holds.

swagger read 2

Here we see that we have a option to try out the API, with description of what to expect from the API. Let’s hit ‘Try it out’ option and check if it really works.

swagger read 3

Here we see that, we are able to execute the API from swagger UI and are presented with response along with other information.

Using swagger for GET API with a request parameter

Let’s look at another read operation which takes a request parameter.

Below is another read API which takes username as request parameter and fetches the info of that user.

//Reading single record
/**
 * @swagger
 * /read/{username}:
 *   get:
 *     summary: Returns the user
 *     parameters:
 *        - in: path
 *          name: username
 *          schema:
 *              type: string
 *              required: true
 *          description: username to find the user
 *     responses:
 *       200:
 *         description: Returns the user
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                  id:
 *                      type: string
 *                      description: auto generated in database
 *                  username:
 *                      type: string
 *                      description: username for user
 *                  nickname:
 *                      type: string
 *                      description: nickname for user
 *                  password:
 *                      type: string
 *                      description: password
 *       404:
 *          description: user not found
 *       500:
 *          description: server error
 */
router.get('/read/:username', async (req,res)=>{
    try{
        const username = req.params.username
        const result = await User.findOne({username});
        if(result)
            return res.send(result)
        else 
            return res.status(404).send(`${username} does not exist`)
    }catch(err){
        res.status(500).send(err)
    }
})

Now in this API we have request parameter. For request parameter we added a parameter section in swagger comments, which takes ‘in’ key to tell swagger where to look for it, which in our case is, in the path. Then we give the name, schema and description for that parameter.

We have also added one extra step where we have defined schema for the response which we will receive from API.

swagger read 4

In the UI we see that we have our read API defined, where it is taking the username parameter as input and also showing us the response schema to expect from the API. Let’s execute it and see the result.

swagger read 5

Here we have executed the API and get the above result.

Using swagger for POST API with a request body

Let’s see how we can define swagger comments for a post API which accepts a request body which contains a user information and adds that user in database.

/**
 * @swagger
 * components:
 *   schemas:
 *     User:
 *       type: object
 *       required:
 *         - username
 *         - password
 *       properties:
 *         username:
 *           type: string
 *           description: username for user
 *         nickname:
 *           type: string
 *           description: nickname for user
 *         password:
 *           type: string
 *           description: password
 *       example:
 *         username: swagger
 *         nickname: openAPI
 *         password: test@123
 */

//C-Create
/**
 * @swagger
 * /create:
 *   post:
 *     summary: creates a new user
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/User'
 *     responses:
 *       200:
 *         description: creates user
 *       500:
 *          description: error
 */
router.post('/create', async (req,res)=>{
    try{
        const user = new User(req.body)
        user.password = bcrypt.hashSync(user.password,bcrypt.genSaltSync(10))
        const result = await User.create(user);
        //const result = await user.save();  this can also be used
        res.send(`${result.username} successfully created!!`)
    }catch(err){
        res.status(500).send(err.message)
    }
})

Here we have defined a post request which accepts a request body. For request body we have created a separate schema first, so we can use it without defining it every time we need it in any other API request. Also along with schema definition, we can also give a example for the same as shown above. We imported that request body schema in the swagger definition for the API using the $ref key. Rest of the steps are almost same as the above APIs.

swagger post 0

Here we see that our schema is listed separately along with our API definition.

swagger post 2

Now in UI, for this API we see that we have a request body defined with example. Now if we execute it, it presents this example by default which we can edit and passes it to request body. Lets execute to see the result.

swagger post 2

Here we see that we presented with same example that we defined in swagger definition to pass as the request body and we are getting the expected result on execution.

Using swagger for PUT API documentation

Here we have a Update API which accepts a request body with username and nickname and updates the nickname with value passed in body for that username.

//Update
/**
 * @swagger
 * /update:
 *   put:
 *     summary: Updates nickname of a user
 *     requestBody:
 *      required: true
 *      content:
 *          application/json:
 *              schema:
 *                  type: object
 *                  required:
 *                      - username
 *                      - nickname
 *                  properties:
 *                      username:
 *                          type: string
 *                          description: username for user
 *                      nickname:
 *                          type: string
 *                          description: nickname for user
 *                  example:
 *                      username: swagger
 *                      nickname: openAPI 3.0
 *     responses:
 *       200:
 *         description: Returns the updated user
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *       500:
 *          description: failed operation
 */
router.put('/update', async (req,res)=>{
    try{
        const username = req.body.username
        const newNickName = req.body.nickname
        const result = await User.findOneAndUpdate(
            { username: username },
            {
              $set: {
                nickname: newNickName,
              }
            },
            { new: true }
          );
        res.send(result)
    }catch(err){
        res.status(500).json(err.message)
    }
})

Here we have followed another approach, as we already learned how we can define schema separately, and then import in swagger definition for the API, here let’s see how we define it in the definition itself. As you can see, under the request body we have defined our schema for it, which is almost same as how we defined it above.

swagger put 1

Here we see the same result as we saw for post API, where we have our API definition with example value for request body.

Using swagger for DELETE API documentation

Let’s look at one last API for delete operation which takes in a username in request body and deletes the user record associated with that username, returning the deleted user object.

/**
 * @swagger
 * /delete:
 *   delete:
 *     summary: deletes a user
 *     requestBody:
 *      required: true
 *      content:
 *          application/json:
 *              schema:
 *                  type: object
 *                  required:
 *                      - username
 *                  properties:
 *                      username:
 *                          type: string
 *                          description: username for user
 *                  example:
 *                      username: swagger
 *     responses:
 *       200:
 *         description: Returns the result of delete operation
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *       500:
 *          description: failed operation
 */
router.delete('/delete', async (req,res)=>{
    try{
        const username = req.body.username
        const result = await User.deleteOne({username});
        res.send(result)
    }catch(err){
        res.status(500).json(err.message)
    }
})

Here also we are defining the request body schema in the definition itself with a example.

As you can see UI, we have our delete API created with request body example.

Here we have successfully executed the API and obtained the response as expected.

Tagging Swagger API definitions

Now you will notice that we have a default title at the top, which does not really help much in explaining what the below APIs are for.

Using Swagger to Autogenerate OpenAPI Specifications

We can make use of tags to group the API definitions under a meaningful title. All we have to do is add a tags key in swagger definition as below

/**
 * @swagger
 * /read:
 *   get:
 *     summary: Returns the list of all users
 *     tags: ['Read Operation']
 *     responses:
 *       200:
 *         description: the list of all users
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *       500:
 *         description: internal error
 */
router.get('/read', async (req,res)=>{
    try{
        const result = await User.find();
        return res.send(result)
    }catch(err){
        res.status(500).send(err)
    }
})

Similarly we can add it for other APIs and we will get the below result.

swagger tagging

Summary

In this article we learned, what is openAPI and swagger and how we can autogenerate REST APIs documentation with the help of swagger. We also learned different ways in which a swagger definition can be built for APIs. We explored swagger UI and how we can use it to test REST APIs.

Also checkout this article to learn how to implement JWT Authentication in NodeJS.

Leave a Reply

Your email address will not be published. Required fields are marked *