REST API with Node.js and Swagger
In this tutorial I’ll show how to piece together the required NPM modules to build a REST API in Node.js with proper Swagger documentation. We’re going to use Express as the HTTP framework, and the Swagger documentation will be written as inline comments within the code, as close as possible to the handling endpoint or models that will implement the contract, so it will be harder for them to eventually diverge.
First of all, these are the main modules we are going to use:
- swagger-jsdoc to extract the documentation from the comments in the source code
- swagger-ui-express to build on-the-fly an HTML version of the documentation (which will allow us also to live test it)
- swagger-model-validator to check that the input & outputs of the request match the models defined for the endpoints
Let’s start with the handlers for the REST endpoints:
// api/controllers/stocks.js
router.get('/', (req, res, next) => {
const response = dao.retrieveAll()
res.send(response)
})
router.get('/:id', (req, res, next) => {
const response = dao.retrieve(parseInt(req.params.id, 10))
res.send(response)
})
router.put('/:id', (req, res, next) => {
const response = dao.update(parseInt(req.params.id, 10), req.body.lastUpdate)
res.send(response)
})
router.post('/', (req, res, next) => {
const response = dao.create(req.body)
res.send(response)
})
Nothing especial hear, just plain Express handlers, using GET, PUT and POST HTTP verbs.
Now let’s write the Swagger documentation as JSON documents within JS multiline comments:
// api/models/stock-model.js
/**
* @swagger
* definitions:
* Stock:
* type: object
* required:
* - id
* - name
* - currentPrice
* - lastUpdate
* properties:
* id:
* type: number
* name:
* type: string
* currentPrice:
* type: number
* lastUpdate:
* type: number
* Stocks:
* type: array
* items:
* $ref '#definitions/Stock'
*/
export default class Stock {
constructor (id, name, currentPrice, lastUpdate) {
this.id = id
this.name = name
this.currentPrice = currentPrice
this.lastUpdate = lastUpdate
}
}
// api/controllers/stocks.js
/**
* @swagger
* /stocks:
* get:
* description: Retrieve the full list of stocks
* tags:
* - stocks
* produces:
* - application/json
* responses:
* 200:
* description: stocks
* schema:
* $ref: '#/definitions/Stocks'
*/
router.get('/', (req, res, next) => {
const response = dao.retrieveAll()
res.send(response)
})
/**
* @swagger
* /stocks/{id}:
* get:
* description: Retrieve an specific stock
* tags:
* - stocks
* produces:
* - application/json
* parameters:
* - name: id
* description: id of the stock to retrieve
* in: path
* required: true
* type: number
* responses:
* 200:
* description: stock
* schema:
* $ref: '#/definitions/Stock'
*/
router.get('/:id', (req, res, next) => {
const response = dao.retrieve(parseInt(req.params.id, 10))
res.send(response)
})
...
Now that we have all the documentation in place, let’s define a new endpoint to serve the live documentation:
// api/controllers/swagger.js
const options = {
swaggerDefinition: {
info: {
title: 'REST - Swagger',
version: '1.0.0',
description: 'REST API with Swagger doc',
contact: {
email: 'contact@danielpecos.com'
}
},
tags: [
{
name: 'stocks',
description: 'Stocks API'
}
],
schemes: ['http'],
host: 'localhost:3000',
basePath: '/api'
},
apis: ['./api/controllers/stocks.js', './api/models/stock-model.js']
}
const swaggerJSDoc = require('swagger-jsdoc')
const swaggerUi = require('swagger-ui-express')
const swaggerSpec = swaggerJSDoc(options)
router.get('/json', function (req, res) {
res.setHeader('Content-Type', 'application/json')
res.send(swaggerSpec)
})
router.use('/', swaggerUi.serve, swaggerUi.setup(swaggerSpec))
As you can see in the next screenshot, the docs are live and working against the endpoints served by the same application:
And you can even test them in realtime!
Finally let’s put some code that will allow us to check that the inputs & outputs of the different endpoints match the models defined in Swagger:
// api/controllers/stocks.js
...
router.put('/:id', (req, res, next) => {
Swagger.validateModel('TimeStamp', req.body)
const response = dao.update(parseInt(req.params.id, 10), req.body.lastUpdate)
Swagger.validateModel('Stock', response)
res.send(response)
})
...
Now we have a proper (and documented) REST API that we can hand over to any developer, making her life easier and more productive.
You can find this example in this GitHub repository: