Hire the author: Lakshay G
What is Swagger?
Swagger is an open-source tool that allows you to describe the structure of your APIs so that machines can read them. Why is it so great? Well, by reading your API’s structure, it can automatically build beautiful and interactive API documentation. It also helps in testing the API’s endpoints.
This project is available here.
In this blog, we will document 5 RESTful API endpoints using swagger. These endpoints are described below:
URL | HTTP Verb | Action |
---|---|---|
/api/fruits | GET | Returns all fruits |
/api/fruits/:_id | GET | Returns a single fruit |
/api/fruits | POST | Add a fruit |
/api/fruits/:_id | PUT | Update a Fruit |
/api/fruits/:_id | DELETE | Delete a Fruit |
Motivation
It becomes really difficult for new employees to understand RESTFUL API documentation written in plain text by some other employee. Thus motivating me to do this project.
Goal
My goal with this project is to implement a frontend based API documentation which is easily understandable by everyone and as a result, the API testing becomes easy and efficient.
Glossary
REST: Representational state transfer (a software architectural style that defines a set of constraints to be used for creating Web services).
Schema: A schema defines the structure of the document, default values, validators, etc.
YAML: It stands for YAML Ain’t Markup Language, a recursive acronym, to distinguish its purpose as data-oriented, rather than document markup.
Project Requirements
- Basic programming fundamentals are a must
- Must have a little bit idea about Node.js and JavaScript
Step by Step Tutorial
1. This project uses MongoDB, so the first step is to create a fruits schema as defined below
const mongoose = require("mongoose"); | |
const fruitsSchema = new mongoose.Schema({ | |
name: { | |
type: String | |
} | |
}); | |
module.exports = fruits = mongoose.model("fruits", fruitsSchema) |
I am using only a single property (i.e name) so that the project remains short and informative.
2. The second step for API documentation is to generate the swagger specification
The swagger specification is generated inside the server.js file with the help of an npm package namely swagger-jsdoc .
After generating the swagger specification we have to set up and serve it with swagger-ui-express.
This is the server.js file below:
require('./config/config') | |
const express = require('express') | |
const mongoose = require('mongoose'); | |
mongoose.set('useFindAndModify', false); | |
const app = express() | |
const bodyParser = require('body-parser') | |
const swaggerJsonDoc = require('swagger-jsdoc') | |
const swaggerUI = require('swagger-ui-express') | |
const swaggerOptions = { | |
swaggerDefinition: { | |
info: { | |
title: "Documenting REST API's", | |
description: "This is an implementation of how to document your RESTful API's using SWAGGER", | |
servers: ['http://localhost:3000'] | |
}, | |
"components": { | |
"schemas": { | |
"fruits": { | |
"properties": { | |
"name": { | |
"type": "string" | |
} | |
} | |
} | |
} | |
} | |
}, | |
apis: ['./routes/api/fruits.js'] | |
} | |
const swaggerDocs = swaggerJsonDoc(swaggerOptions) | |
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocs)) | |
app.use(bodyParser.urlencoded({ | |
extended: false | |
})) | |
app.use(bodyParser.json()) | |
app.use("/api", require('./routes/api/fruits.js')) | |
mongoose.connect(process.env.URLDB, { | |
useNewUrlParser: true, | |
useUnifiedTopology: true, | |
useCreateIndex: true | |
}).then(response => { | |
console.log('Mongo DB Connected') | |
}).catch(error => { | |
console.log(error) | |
}) | |
app.listen(process.env.PORT, () => { | |
console.log(`Server started on ${process.env.PORT}`) | |
}) |
Let us understand the code block by block, in order to get a better understanding of swagger.
The first block includes the requiring of the npm packages so that these packages can be used inside our application.
The second block includes the definition of swagger options which are automatically converted into swagger docs(swagger specification) with the help of swagger-jsdoc package.
The swagger options also consist of two parts: swagger definition and APIs
– swagger definition: The info part contains the title, description, and the server on which the app will be running. The components part consist of all other things like various schemas used in the project (fruits in our case)
–APIs: This is an array of paths of different APIs.
After this, serve the swagger specification at /api-docs endpoint.
After this run the server in order to check if swagger is successfully set-up or not, you should see something like this:
const swaggerOptions = { | |
swaggerDefinition: { | |
info: { | |
title: "Documenting REST API's", | |
description: "This is an implementation of how to document your RESTful API's using SWAGGER", | |
servers: ['http://localhost:3000'] | |
}, | |
"components": { | |
"schemas": { | |
"fruits": { | |
"properties": { | |
"name": { | |
"type": "string" | |
} | |
} | |
} | |
} | |
} | |
}, | |
apis: ['./routes/api/fruits.js'] | |
} | |
const swaggerDocs = swaggerJsonDoc(swaggerOptions) | |
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(swaggerDocs)) |
3. The third and final step is to create the routes and update the route handlers(the most important step towards interactive API documentation)
swagger-jsdoc uses JSDoc-type comments to generate the Swagger specification. So, add such comments, in YAML, to the route handlers that describe their functionality
/** | |
* @swagger | |
* /api/fruits: | |
* get: | |
* tags: | |
* - Fruits | |
* description: Returns all fruits | |
* produces: | |
* - application/json | |
* responses: | |
* 200: | |
* description: An array of fruits | |
* schema: | |
* $ref: '#/components/schemas/fruits' | |
* 500: | |
* description: SERVER ERROR | |
*/ | |
router.get('/fruits', async(req, res) => { | |
try { | |
var fruits = await Fruits.find(); | |
//console.log(fruits) | |
return res.json({ | |
fruits | |
}) | |
} catch (err) { | |
res.status(500).send('Server error') | |
} | |
}) |
This is pretty much self explanatory. We have an
/api/fruits
endpoint that returns two possible responses 200(successful) and 500(server error) to our GET request (you can add more like 401 error for unauthorized access)$ref is used to refer to the response schema (defined in the components sections while generating swagger specs.) .
Similarly add such comments for other RESTful API’s also. Refer the code below :
const express = require("express"); | |
const router = express.Router(); | |
var Fruits = require('../../model/fruits.js') | |
/** | |
* @swagger | |
* /api/fruits: | |
* get: | |
* tags: | |
* - Fruits | |
* description: Returns all fruits | |
* produces: | |
* - application/json | |
* responses: | |
* 200: | |
* description: An array of fruits | |
* schema: | |
* $ref: '#/components/schemas/fruits' | |
* 500: | |
* description: SERVER ERROR | |
*/ | |
router.get('/fruits', async(req, res) => { | |
try { | |
var fruits = await Fruits.find(); | |
//console.log(fruits) | |
return res.json({ | |
fruits | |
}) | |
} catch (err) { | |
res.status(500).send('Server error') | |
} | |
}) | |
/** | |
* @swagger | |
* /api/fruits/{_id}: | |
* get: | |
* tags: | |
* - Fruits | |
* description: Returns a single fruit | |
* produces: | |
* - application/json | |
* parameters: | |
* - name: _id | |
* description: Particular Fruit Object's ID (Automatically assigned by MongoDB) | |
* in: path | |
* required: true | |
* type: string | |
* responses: | |
* 200: | |
* description: A single fruit | |
* schema: | |
* $ref: '#/components/schemas/fruits' | |
* 500: | |
* description: Server Error | |
*/ | |
router.get('/fruits/:_id', async(req, res) => { | |
try { | |
var objectId = req.params._id; | |
var fruit = await Fruits.findOne({ | |
"_id": objectId | |
}); | |
if (fruit) { | |
return res.json(fruit) | |
} else { | |
return res.send(`No fruit with the ${objectId} ID is present in the database`) | |
} | |
} catch (err) { | |
console.log(err) | |
res.status(500).send('Server error') | |
} | |
}) | |
/** | |
* @swagger | |
* /api/fruits: | |
* post: | |
* tags: | |
* - Fruits | |
* description: Adds a new fruit to the database | |
* produces: | |
* - application/json | |
* parameters: | |
* - name: fruit | |
* description: Fruit object | |
* in: body | |
* required: true | |
* schema: | |
* $ref: '#/components/schemas/fruits' | |
* responses: | |
* 200: | |
* description: Successfully added | |
* 500: | |
* description: Server error | |
*/ | |
router.post('/fruits', async(req, res) => { | |
try { | |
var name = req.body.name; | |
if (name) { | |
var fruit = new Fruits({ | |
"name": name | |
}) | |
fruit.save(); | |
return res.send(`Added ${name} to the DB`) | |
} else { | |
return res.send('Enter the name of the fruit in the body') | |
} | |
} catch (err) { | |
res.status(500).send('Server Error') | |
} | |
}) | |
/** | |
* @swagger | |
* /api/fruits/{_id}: | |
* put: | |
* tags: | |
* - Fruits | |
* description: Updates a single fruit | |
* produces: | |
* - application/json | |
* parameters: | |
* - name: fruit | |
* description: Fruit object resources | |
* in: body | |
* required: true | |
* schema: | |
* $ref: '#/components/schemas/fruits' | |
* - name: _id | |
* description: Fruit Object ID | |
* in: path | |
* required: true | |
* responses: | |
* 200: | |
* description: Successfully added | |
* 500: | |
* description: Server error | |
*/ | |
router.put('/fruits/:_id', async(req, res) => { | |
try { | |
var objectId = req.params._id; | |
var name = req.body.name; | |
if (objectId && name) { | |
await Fruits.findByIdAndUpdate({ | |
"_id": objectId | |
}, req.body); | |
return res.json({ | |
"status": "Successully updated" | |
}) | |
} else { | |
res.send('Check your inputs!!') | |
} | |
} catch (err) { | |
res.status(500).send('Server error'); | |
} | |
}) | |
/** | |
* @swagger | |
* /api/fruits/{_id}: | |
* delete: | |
* tags: | |
* - Fruits | |
* description: Deletes a single fruit | |
* produces: | |
* - application/json | |
* parameters: | |
* - name: _id | |
* description: Fruit Object ID | |
* in: path | |
* required: true | |
* responses: | |
* 200: | |
* description: Successfully deleted | |
* 500: | |
* description: Server error | |
*/ | |
router.delete('/fruits/:_id', async(req, res) => { | |
try { | |
var objectId = req.params._id; | |
if (await Fruits.findOne({ | |
"_id": objectId | |
})) { | |
await Fruits.findByIdAndDelete({ | |
"_id": objectId | |
}) | |
return res.json({ | |
"status": "Successfully deleted" | |
}) | |
} else { | |
return res.send(`No fruit with ${objectId} ID exists in the database`) | |
} | |
} catch (err) { | |
res.status(500).send("Server error") | |
} | |
}) | |
module.exports = router |
With this step, our coding part is complete, so now in order to run and test it, Run the command node server.js and visit localhost:3000/api-docs to view the API documentation with Swagger UI. Here you can test your API’s by clicking on the respective API .

Learning Tools and Strategies
- Don’t write messy code, everything should be well understood. As a result, it will help you in debugging.
- Do a console.log() at each major step you feel is important or you feel maybe will throw some kind of error so that you are able to get the result of each major code-snippet.
- Another thing is to go through the documentation of swagger-jsdoc and swagger-ui-express thoroughly.
Search terms
test, api, document, swagger, interactive api documentation
Workaround
I prefer working in a quiet environment so that I am completely focused
Reflective Analysis
Learning how to use swagger was a fun learning experience. It is an amazing tool that has makes the understanding and testing of API endpoints easy. A very common error that is expected while using swagger is the indentation of the comments, so be careful with that.
Conclusion
This project covers the documentation and testing of 5 basic RESTful API endpoints without authorizing and authenticating the user. How about a complete project that also includes authorization? Let me know your thought below on the comment section. Overall, I feel swagger is a great tool to do interactive documentation of your API’s endpoint. Refer to the documentation for some help.
This project is available here.
Great work on the tutorial and project.
I noticed in your motivation that the reason you use swagger with your APIs is to help new employee understand the API endpoints. Do you think that should be the only reason for using Swagger?
What made you choose Swagger over other tools such as Apairy?
Not really, apart from apiary and swagger there are many others like postman is one of them which is widely used for understanding the api endpoints
The fact that I have chosen swagger is its interactive UI and easy implementation and also apiary offers limited functionality as compared to swagger
Thanks for the comparison above. I have been working with apiary for a while but given the above info, moving forward I will be in a better position in choosing what tool to use and where to use it.
Great job.
This is what I was looking for, amazing work man!!