DOCUMENTING YOUR API WITH SWAGGER
Tags: swagger, node, api, documentation
Published: 2022-08-02 18:17:39

Ever gone through the engineering your API's knowing fully well, you will need to come up with a documentation so other devs can work with it?


You have a short answer here. I will try to make this docs as simple and comprehensive as it might be. Might not be as comprehensive as you want, but you willl be able to scale it up to meet your demands.

Stacks and technologies needed: Node.js, Express, MongoDB, Swagger.

Initialize a project using `npm init`. 

Follow the step by step guideline/prompt...

You can go ahead to install express and Mongo and some other dependecies necessary 

```npm i express mongoose nodemon dotenv cors```

- Express will be used for the middleware to create various CRUD endpoints.
- Mongoose for managing data in MongoDB.
- Nodemon to restart our server every time we save our file.
- Dotenv to manage a .env file.
- cors to manage and provide a middleware that can be used to enable CORS with various options

Once the installation is complete, go ahead to create an index.js in the root directory of your application.

``` 
const express = require('express');
const mongoose = require('mongoose');

const app = express();

app.use(express.json());

app.listen(3000, () => {
    console.log(`Server Started at ${3000}`)
})
```

Things to note from the code lines above:
We have imported express and mongoose as dependencies and assigned express to app. 
Now we can listen on the port we assigned once we start the server.

In _package.json_ file, you can add the line below:
```json 
"scripts": {
    "start": "nodemon index.js"
},
```
Remember **Nodemon** will help us auto-restart the server on code changes.

You can just do a `npm start` to start your node server. 

We won't be going deep into getting our MongoDB ready and set up in this tutorial course. You can check this out from  [this section.io post](https://www.section.io/engineering-education/nodejs-mongoosejs-mongodb/)
** I will be using MongoDB Compass which allows for local development and testing.




After setting up our DB, we can create a `.env` file and add our DB details.
```
MONGO_DB=mongodb://localhost:27017/
PORT=3000
```
The value for MONGO_DB in your .env might be different depending on the MongoDB URL you get (compass/atlas).

Now we can modify `index.js` file to effect some needed changes.
```
require("dotenv").config();
const cors = require("cors");
const express = require("express");

const { connect } = require("mongoose");
const app = express();
 
app.use(cors());
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
 
const runApp = async () => {
    try {
        await connect(process.env.MONGO_DB, {
            useUnifiedTopology: true,
            useNewUrlParser: true,
        });
        console.log(`Successfully connected to database ${process.env.MONGO_DB}`);
        app.listen(process.env.PORT, () => {
            console.log(`Server started successfully on PORT ${process.env.PORT}`);
        })
    } catch(err) {
        console.log(err);
        runApp();
    }
};
 
runApp();

```

Using `dotenv`  allows for .env variables to be accessible on this page.
In the try block, we put our .env variables to use there. 

Running `npm start` should give us this result below

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9lg46owik46e6rw3kpz.png)

If you get that result, congratulations. Let us go ahead and install our swagger dependecies.

```npm i swagger-autogen swagger-ui-express```

## Creating Routes for our endpoints
Create a folder called **routes** and inside, make a file called **index.js**
Before proceeding, let's import this file into our main script file.

Our main script file should look like this now: 
```
require("dotenv").config();
const cors = require("cors");
const express = require("express");

const { connect } = require("mongoose"); 
const app = express();
const router = require("./routes/index");
 
app.use(cors());
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(router);
 
const runApp = async () => {
    try {
        await connect(process.env.MONGO_DB, {
            useUnifiedTopology: true,
            useNewUrlParser: true,
        });
        console.log(`Successfully connected to database ${process.env.MONGO_DB}`);
        app.listen(process.env.PORT, () => {
            console.log(`Server started successfully on PORT ${process.env.PORT}`);
        })
    } catch(err) {
        console.log(err);
        runApp();
    }
};
 
runApp();

```
We will be getting an error now because our `routes/index.js` file is empty.

** We are going to create a simple student database, no authentication or authorization needed. 

Let's create a `students.js` file in our routes folder.
Add the following content:
```
    const router = require("express").Router();

    router.get("/students", async (req, res) => {
        res.send('Get All students')
    })

    //Post 
    router.post('/students', async (req, res) => {
        res.send('Add a new student')
    })

    //Get by ID 
    router.get('/students/get/:id', async (req, res) => {
        res.send('Get by one student by ID')
    })

    //Update by ID 
    router.patch('/students/update/:id', async (req, res) => {
        res.send('Update student by ID')
    })

    //Delete by ID 
    router.delete('/students/delete/:id', async (req, res) => {
        res.send('Delete by ID')
    })


    module.exports = router
```

Now we can go back to our `routes/index.js` file to import our students file. This method allows us to define different routes for different collections in our DB.

We have five methods that use the REST Methods for our CRUD operation. 

This router is taking the route as the first parameter. Then in the second parameter it's taking a callback.

In the callback, we have a res and a req. res means response, and req means request. We use res for sending responses to our client, like Postman, or any front-end client. And we use req for receiving requests from a client app like Postman, or any front-end client.

Then in the callback body, we are printing a message that says the respective API message.


Let's populate our `routes/index.js` with code below:
```
const router = require("express").Router();

const studentRoutes = require('./students.js');

router.use("/api", studentRoutes); 

module.exports = router;
```
Checking our server instance now, there won't be any process if you followed through to this point.

## Creating Models for our DB structure
Create a folder called model and inside, a file called student.js.

```
const mongoose = require('mongoose');

const studentSchema = new mongoose.Schema({
    firstname: {
        required: true,
        type: String
    },
    lastname: {
        required: true,
        type: String
    },
    middlename: {
        required: false,
        type: String
    },
    class: {
        required: true,
        type: String
    },
    age: {
        required: true,
        type: Number
    }
})

module.exports = mongoose.model('Student', studentSchema)
```
Here, we have a schema that defines our database structure. It has some properties. The fields have types with some required and some optional.

## Creating Controllers for our APIs
Create a folder called controllers. This helps abstract our functions and make out routes files less *bulky and buggy*

We can then create a `students.js` file in the controllers folder
```
const Student = require("../model/students.js");
 
const addOne = async(req, res) => {
    try {
        const newRecord = new Student({
            ...req.body,
        });
        await newRecord.save();
        return res.status(201).json({
            message: "Student successfully created",
            success: true
        });
    } catch(err) {
        return res.status(500).json({
            message: err.message,
            success: false,
        });
    }
};
 
const removeOne = async(req, res) => {
    try {
        const deleted = await Student.findByIdAndDelete(req.params.id);
        if(!deleted) {
            return res.status(404).json({
                message: "Student not found",
                success: false,
            });
        }
        return res.status(204).json({
            message: "Student successfully deleted",
            success: true,
        });
    } catch(err) {
        return res.status(500).json({
            message: err.message,
            success: false,
        });
    }
};
 
const updateOne = async(req, res) => {
    try {
        await Student.findByIdAndUpdate(req.params.id, req.body);
        return res.status(201).json({
            message: "Student's data successfully updated",
            success: true,
        });
    } catch(err) {
        return res.status(500).json({
            message: err.message,
            success: false,
        });
    }
};
 
const getAll = async(req, res) => {
    try {
        const [results, studentCount] = await
        Promise.all([
            Student.find({}) 
                .sort({createdAt: -1})
                .limit(req.query.limit)
                .skip(req.skip)
                .lean()
                .exec(),
                Student.count({}),
        ]);
        const pageCount = Math.ceil(studentCount / req.query.limit);
        return res.status(201).json({
            data: results,
            pageCount,
            studentCount,
        });
    } catch(err) {
        return res.status(500).json({
            message: err.message,
            success: false,
        });
    }
};
 
const getOne = async(req, res) => {
    try {
        const student = await Student.findById(req.params.id);
        if(student) {
            return res.status(200).json(student);
        }
        return res.status(404).json({
            message: "Student not found",
            success: false,
        });
    } catch(err) {
        return res.status(500).json({
            message: err.message,
            success: false,
        });
    }
};
 
module.exports = {
    addOne,
    removeOne,
    updateOne,
    getAll,
    getOne
}
```

Now, we can go back to `routes/students.js` to make use of these functions.
```

    const router = require("express").Router();
    const { addOne, removeOne, updateOne, getAll, getOne } = require("../controller/students.js");


    router.get("/students", async (req, res) => {
        await getAll(req, res);
    })

    //Post 
    router.post('/students', async (req, res) => {
        await addOne(req, res);
    })

    //Get by ID 
    router.get('/students/get/:id', async (req, res) => {
        await getOne(req, res);
    })

    //Update by ID 
    router.patch('/students/update/:id', async (req, res) => {
        await updateOne(req, res);
    })

    //Delete by ID 
    router.delete('/students/delete/:id', async (req, res) => {
        await removeOne(req, res);
    })


    module.exports = router
```

## Swagger APIs 
We are going to generate our API documentation from code based on comments  in the function headers
Back to `routes/students.js` we are going to add **swagger.tags** to each of the route. This creates a subsection in our docs page with the tag. (It groups tags with the same name together).
Remember, we are enclosing it in a comment block.

```
/*  
	#swagger.tags = ['Students']
*/  
```
Thats what we are adding to each of the routes. Now our `students.js` routes should look like this:
```
    const router = require("express").Router();
    const { addOne, removeOne, updateOne, getAll, getOne } = require("../controller/students.js");


    router.get("/students", async (req, res) => {
        /*  
            #swagger.tags = ['Students']
        */ 
        await getAll(req, res);
    })

    //Post 
    router.post('/students', async (req, res) => {
        /*  
            #swagger.tags = ['Students']
        */ 
        await addOne(req, res);
    })

    //Get by ID 
    router.get('/students/get/:id', async (req, res) => {
        /*  
            #swagger.tags = ['Students']
        */  
        await getOne(req, res);
    })

    //Update by ID 
    router.patch('/students/update/:id', async (req, res) => {
        /*  
            #swagger.tags = ['Students']
        */ 
        await updateOne(req, res);
    })

    //Delete by ID 
    router.delete('/students/delete/:id', async (req, res) => {
        /*  
            #swagger.tags = ['Students']
        */ 
        await removeOne(req, res);
    })


    module.exports = router
```
To be able to see our changes to the route file, we should add a new command to our `package.json` file. 
```
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js",
    "start-gendoc": "node swagger.js" 
  },
```
Our scripts in `package.json` should be like the one above.

The last line will help us generate a json file for swagger that we can view on our browser environment.

Let's go ahead and create a `swagger.js` file in our root directory.
```
const swaggerAutogen = require("swagger-autogen")()
require("dotenv").config();
 
const doc = {
    info: {
        version: "1.0.0",
        title: "Quick start to auto-docs with swagger",
        description: "Auto-documenting our APIs with Swagger documentation. Tutorial by Daniel Olabemiwo"
    },
    host: "localhost:3000", // 3000 is our port
    basePath: "/",
    schemes: ["http", "https"],
    consumes: ["application/json"],
    produces: ["application/json"],
    tags: [
    ],
    definitions: {
        StudentModel: {
            $firstname: "Ade",
            $lastname: "Samuel",
            $middlename: "Daniel",
            $class: "BASIC 3",
            $age: 10
        },
    }
};

 
const outputFile = "./swagger_output.json";
const endpointFiles = ["./routes/index.js"];
 
swaggerAutogen(outputFile, endpointFiles, doc).then(() => {
    require("./index");
});
```
This is how our `swagger.js` file will look like.

We should also add some **swagger lines** to our  root **index.js** file.
It should look like this:

```
require("dotenv").config();
const cors = require("cors");
const express = require("express");

const { connect } = require("mongoose");
const swaggerUi = require("swagger-ui-express");
const swaggerFile = require("./swagger_output.json");
 
const app = express();
const router = require("./routes/index");
 
app.use(cors());
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(router);
app.use("/doc", swaggerUi.serve, swaggerUi.setup(swaggerFile));
 
const runApp = async () => {
    try {
        await connect(process.env.MONGO_DB, {
            useUnifiedTopology: true,
            useNewUrlParser: true,
        });
        console.log(`Successfully connected to database ${process.env.MONGO_DB}`);
        app.listen(process.env.PORT, () => {
            console.log(`Server started successfully on PORT ${process.env.PORT}`);
        })
    } catch(err) {
        console.log(err);
        runApp();
    }
};
 
runApp();
```
Look out for all the new addition to our index.js file.

We should stop our server and run this new command.
```
npm run start-gendoc
```

Let's visit our browser and check out [http://localhost:3000/doc/](http://localhost:3000/doc/)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8cl3r9ud628bq0h6bnxr.png)


Congratulations if your browser is looking like the one above!
Now let's wrap up and add necessary parameters to our post request as it requires parameters from users.
```
    //Post 
    router.post('/students', async (req, res) => {
         /*  
         #swagger.tags = ['Students'],
        #swagger.security = [{
        "Authorization": []
        }]
        #swagger.parameters['obj'] = {
            in: 'body',
            required: true,
            schema: { $ref: "#/definitions/StudentModel" }
    } */ 
        await addOne(req, res);
    })

    
```
We have to rerun `npm run start-gendoc` to reflect all the newest additions.
If you followed this tutorial to this point, you can test the `POST` endpoint on your browser and should get a result like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z6qjicrm0s9il555v70c.png)

This is the end of this tutorial session. 

Feel free to drop your comments, or connect with me via [twitter](https://www.twitter.com/fikkyman1) or [linkedIn](https://www.linkedin.com/in/d-lite)