Document Nodejs API with Swagger

przpiw

Damian Piwowarczyk

Posted on January 21, 2022

Document Nodejs API with Swagger

What is an API?

API documentation is a technical manual, containing information on how to consume API and how to use it. Documentation also describes what type of data format API is expecting in request and what types are returned from it.

Why do we need API documentation?

As with every technology, there must be a guide to help understand others how to use it. API documentation helps people do understand what type of operation can be performed and what resources they can be accepted & retrieved. Swagger will help us to make our API easy to ready and test some of the functionality.

Today we will focus on creating a simple REST API and integrating swagger with Open API 3.0 specification. Our documentation will be available in a graphical form accessible through the browser and downloadable JSON format file. JSON file could be imported later to the API testing tool or Postman.
For this example, we will use nodejs with express.

Image api_doc

What our API documentation will consist of?

  • Docs for GET, POST, PUT, DELETE
  • Description of resources
  • Endpoints and methods
  • Schema of Request/Response
  • Datatypes and parameters accepted
  • Examples

Image api_documentation

Lets get started!

├── controllers
│   └── hero.controller.js
├── index.js
├── package.json
├── routes
│   ├── hero.routes.js
│   └── index.js
└── swagger.js
Enter fullscreen mode Exit fullscreen mode

In our directory npm init

npm install express swagger-jsdoc swagger-ui-express

In package.json we will add

"type":"module"
Enter fullscreen mode Exit fullscreen mode

to enable ES6 modules.

In index.js we create basic express app and import our swagger config.

import express from 'express'
import router from './routes/index.js'
import swaggerDocs from './swagger.js'

const app = express()
const port = 5000

app.use(express.json())
app.use(router)

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
  swaggerDocs(app, port)
})
Enter fullscreen mode Exit fullscreen mode

swagger.js contains our configuration.

import swaggerJsdoc from 'swagger-jsdoc'
import swaggerUi from 'swagger-ui-express'

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'Hero API',
      description: 'Example of CRUD API ',
      version: '1.0.0',
    },
  },
  // looks for configuration in specified directories
  apis: ['./routes/*.js'],
}

const swaggerSpec = swaggerJsdoc(options)

function swaggerDocs(app, port) {
  // Swagger Page
  app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))

  // Documentation in JSON format
  app.get('/docs.json', (req, res) => {
    res.setHeader('Content-Type', 'application/json')
    res.send(swaggerSpec)
  })
}

export default swaggerDocs
Enter fullscreen mode Exit fullscreen mode

Now we setup our API routes along with OpenAPI specification represented in YAML format. All fields in the specification are case-sensitive. For better accessibility & readability we will place specifications just above the API routes.

mkdir routes && cd routes
index.js

import express from 'express'
import heroRoutes from './hero.routes.js'
const router = express.Router()

/**
 * @openapi
 * /healthcheck:
 *  get:
 *     tags:
 *     - Healthcheck
 *     description: Returns API operational status
 *     responses:
 *       200:
 *         description: API is  running
 */
router.get('/healthcheck', (req, res) => res.sendStatus(200))

router.use(heroRoutes)

export default route
Enter fullscreen mode Exit fullscreen mode

Our Hero API will consist of 4 requests.
GET, POST, PUT, DELETE
For each of requests we will write specification that will let API users know what type of input our API expect and what it returns along with response codes.
We will keep our logic separately in controllers directory.

import express from 'express'
import {
  getHeroesHandler,
  addHeroHandler,
  deleteHeroHandler,
  editHeroHandler,
} from '../controllers/hero.controller.js'

const router = express.Router()

/**
 * @openapi
 * '/api/heroes':
 *  get:
 *     tags:
 *     - Hero
 *     summary: Get all heroes
 *     responses:
 *       200:
 *         description: Success
 *         content:
 *          application/json:
 *            schema:
 *              type: array
 *              items:
 *                type: object
 *                properties:
 *                  id:
 *                    type: number
 *                  name:
 *                    type: string
 *       400:
 *         description: Bad request
 */

router.get('/api/heroes', getHeroesHandler)

/**
 * @openapi
 * '/api/hero':
 *  post:
 *     tags:
 *     - Hero
 *     summary: Create a hero
 *     requestBody:
 *      required: true
 *      content:
 *        application/json:
 *           schema:
 *            type: object
 *            required:
 *              - id
 *              - name
 *            properties:
 *              id:
 *                type: number
 *                default: 2
 *              name:
 *                type: string
 *                default: New Hero Name
 *     responses:
 *      201:
 *        description: Created
 *      409:
 *        description: Conflict
 *      404:
 *        description: Not Found
 */
router.post('/api/hero', addHeroHandler)

/**
 * @openapi
 * '/api/hero':
 *  put:
 *     tags:
 *     - Hero
 *     summary: Modify a hero
 *     requestBody:
 *      required: true
 *      content:
 *        application/json:
 *           schema:
 *            type: object
 *            required:
 *              - id
 *              - name
 *            properties:
 *              id:
 *                type: number
 *                default: 1
 *              name:
 *                type: string
 *                default: Hulk
 *     responses:
 *      200:
 *        description: Modified
 *      400:
 *        description: Bad Request
 *      404:
 *        description: Not Found
 */
router.put('/api/hero', editHeroHandler)

/**
 * @openapi
 * '/api/hero/{id}':
 *  delete:
 *     tags:
 *     - Hero
 *     summary: Remove hero by id
 *     parameters:
 *      - name: id
 *        in: path
 *        description: The unique id of the hero
 *        required: true
 *     responses:
 *      200:
 *        description: Removed
 *      400:
 *        description: Bad request
 *      404:
 *        description: Not Found
 */
router.delete('/api/hero/:id', deleteHeroHandler)

export default router

Enter fullscreen mode Exit fullscreen mode

Next we will create functions responsible for handling incoming requests and returning appropriate data.

hero_controler.js

let heroes = [
  {
    id: 1,
    name: 'Batman',
  },
  { id: 2, name: 'Spiderman' },
]

export async function getHeroesHandler(req, res) {
  res.status(200).json(heroes)
}

export async function addHeroHandler(req, res) {
  if (heroes.find((hero) => hero.id === req.body.id)) {
    res.status(409).json('Hero id must be unique')
  }
  else{
    heroes.push(req.body)
    res.status(200).json(heroes)
  }
}

export async function deleteHeroHandler(req, res) {
  const index = heroes.findIndex((hero) => hero.id == req.params.id)
  if (index >= 0) {
    heroes.splice(index, 1)
    res.status(200).json(heroes)
  } else res.status(400).send()
}

export async function editHeroHandler(req, res) {
  const index = heroes.findIndex((hero) => hero.id == req.body.id)
  if (index >= 0) {
    heroes.splice(index, 1, req.body)
    res.status(200).json(heroes)
  } else res.status(400).send()
Enter fullscreen mode Exit fullscreen mode

Now we can start our API with node index.js
and navigate to localhost:4000/docs to view our documentation or we can also get JSON format from localhost:4000/docs.json which can be used for testing or perhaps imported to POSTMAN.

To sum up, this is only a simple demo to demonstrate how to use swagger with OpenAPI 3.0 in our express application. I think the swagger is a great tool because it helps us to create clear and neat documentation, provide an excellent visual page for users who could effortlessly and quickly test API functionality themselves.

I hope this article was helpful to some of you guys. Thanks for reading!
Github repo

💖 💪 🙅 🚩
przpiw
Damian Piwowarczyk

Posted on January 21, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related