How to write Interactive API Documentation ?
Tags: api, webdev, backend, beginners
Published: 2024-04-15 18:31:44

## API Documentation Overview 
An API serves as a mediator between applications and web servers, facilitating information sharing. Properly documenting APIs is crucial for a smooth developer experience. API documentation is akin to a user manual, providing guidance on using and integrating with an API effectively. 

![api meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zfdid4jz7wlmbhzdbwbz.jpeg)

It should cover:
- API overview and its purpose
- Step-by-step tutorials and examples
- Glossary of key terms
- Supported operations
- API response details

![meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pc6qh8c9fsk45bl3chey.jpeg)

This article introduces Swagger, an ecosystem of tools for streamlined API development and documentation using the OpenAPI Specification (OAS).

## What is OAS?
OpenAPI specification (previously known as Swagger specification) describes a standard format for REST APIs. This standard is important so that everyone who writes REST APIs is compliant with its best practices such as the versioning, safety, and error handling. We can say that OpenAPI is similar to a template with a set of rules and constraints explaining how you could describe an API. It is usually written in YAML or JSON file format making it readable for humans and machines.

This file let us:-

- Describe all available API endpoints (e.g. `/users`, `/users/{id}`)
- Operations in the endpoints such as `GET /users`, `POST /user`
- Input and Output parameters for each operation
- Authentication mechanisms
- Contact information, API Licence, terms of use and other information


![meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptuyml2wbvif722z3xix.jpeg)

## What is Swagger?
Swagger is a set of open-source tools built around the OpenAPI Specification that can help us to design, build, document and consume REST APIs. The ability of APIs to describe their own structure is the root of all awesomeness in Swagger. Note that Swagger not only helps us to design and document REST APIs, it also lets us build (Server Stub) and Consume (Rest Clients) REST APIs.

The major Swagger tools include:

- Swagger Editor — browser-based editor where one can write OpenAPI specification
- Swagger UI — renders OpenAPI specs as interactive API documentation
- Swagger Codegen — generates server stubs and client libraries from an OpenAPI specification

![swagger logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zjipr7nj70vgfm67fw9u.png)

## Express.js API application setup
Set Up your NodeJS,Express App with all the API Endpoints
Once you have done it, run the commands below in the terminal:

```
npm install
npm i swagger-ui-express swagger-jsdoc
```

_These are going to download the required dependencies into your application. Now it’s time to integrate Swagger into your Node.js application._

## How to connect Swagger to Node.js?
To connect Swagger to your Node.js application, import `swagger-ui-express` and `swagger-jsdoc` in your `server.js` or create a seprate `swagger.js` file for the :

```
  const swaggerJsdoc = require("swagger-jsdoc"),
  const swaggerUi = require("swagger-ui-express");

```

Those are the two respective objects representing the libraries we’ve imported. Next, add the following code before the app’s listen function:


```
const options = {
  definition: {
    openapi: '3.1.0',
    info: {
      title: 'CRUD API Documentation',
      version: '1.0.0',
      description: 'Documentation for the CRUD API created using Node.js, Express, and MongoDB.',
      contact: {
        name: "Himanshu Aggarwal",
        email: "himanshuaggar00@gmail.com",
    },
    license: {
      name: "MIT",
      url: "https://spdx.org/licenses/MIT.html",
    },
    },
    servers: [
      {
        url: 'http://localhost:9000', // Change the URL based on your server configuration
        description: 'Local server',
      },
    ],
  },
  apis: ['./routes/*.js'], // Path to the API routes directory
};

const specs = swaggerJsdoc(options);

module.exports = {
  serve: swaggerUi.serve,
  setup: swaggerUi.setup(specs),
};

```
As you see in the first line, this configuration object sets an OpenAPI to `v3.1.0`.

Swagger uses the Open API Specification, a standard, language-agnostic interface for RESTful APIs that allows humans and machines to understand the capabilities of a web service without having to access the source code or inspect the network traffic.

You can refer to the official docs for all available settings for each version. Here, we’re using just the basics: API `info`, `name`, `title`, `description`, `license`, the `contact` of the API owner, and more.

The API’s property is essential because it searches for the model and endpoint definitions, so inform it correctly.

Finally, we’re using the swaggerJsdoc function to scan through the options passed in as a `param` and return the converted Swagger specification object. This one, in turn, can be used along with the `swaggerUi` setup process.

You can now start the application via the npm start command. You’ll see the following screen when accessing the `http://localhost:9000/api-docs/` 


![swagger img](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9760brwpvf0bxcg58v6.png)


Note, that we still don’t have any operations defined in the spec. This happens because we need to map those operations to the routes explicitly. Otherwise, Swagger can’t figure out the API endpoints on its own.

Optionally, you can add a search bar to your UI just in case your API has too many operations. For this, change the implementation to the following:

```
app.use(
  "/api-docs",
  swaggerUi.serve,
  swaggerUi.setup(specs, { explorer: true })
);
```
Now, the search bar will show up.

## Creating the API model
Like many significant frameworks and API architectures, data is encapsulated into models to become more easily accessible. Swagger also expects your APIs to have models and for you to define them.

Go to routes and you can define the schema at the beginning of the file as shown below:

```

/**
 * @swagger
 * components:
 *   schemas:
 *     Product:
 *       type: object
 *       required:
 *         - name
 *         - brand
 *         - ram
 *         - camera
 *         - network
 *         - fingerprint
 *         - price
 *       properties:
 *         _id:
 *           type: string
 *           description: The auto-generated unique identifier of the product.
 *         name:
 *           type: string
 *           description: The name of the product.
 *         brand:
 *           type: string
 *           description: The brand of the product.
 *         ram:
 *           type: integer
 *           description: The RAM capacity of the product in gigabytes.
 *         camera:
 *           type: string
 *           description: The camera specification of the product.
 *         network:
 *           type: string
 *           description: The network connectivity of the product.
 *         fingerprint:
 *           type: boolean
 *           description: Indicates whether the product has a fingerprint sensor.
 *         price:
 *           type: number
 *           description: The price of the product.
 *       example:
 *         _id: "65d34a7cbe5329a2e035c756"
 *         name: Galaxy S21
 *         brand: Samsung
 *         ram: 8
 *         camera: 64 MP
 *         network: 5G
 *         fingerprint: true
 *         price: 799
 */

```
Remember the JSDocs we’ve talked about? JSDocs now enters the scene and helps us to set up the rest of the Swagger spec definitions through the` @swagger` annotation. Here, you can define as many schemas as you want. In our case, we’re just defining the domain Books.

The required property receives the list of attributes that must be filled in the requests. This step is essential for letting people know what they must send when using your API.


The properties property describes detailed information about your model attributes. Each attribute must have a name followed by its type, description (optional), and format (you can validate values too). Please refer to Swagger data types for a complete list of available data types.

Finally, you can provide an example of request data for this schema model. That’s going to be useful later. When you restart the app and refresh the page, you’ll see the screen below:


![schema](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f7rq7h5vprnu5fal23vf.png)

It’s important to note that if you’re facing a `YAMLSemanticError` in the schema, make sure to check the indentation in the `YAML `configuration

## Integrating operations into the routes

Now, we have Swagger schema integrated into the routes. However, we still don’t have any operations. Let’s fix that. Right after the previous JSDoc comment, add the following:

```
/**
 * @swagger
 * tags:
 *   name: Products
 *   description: TheProducts CRUD API
 * /products:
 *   get:
 *     summary: Retrieve a list of all products.
 *     description: Retrieve a list of all products from the database.
 *     responses:
 *       200:
 *         description: A list of products.
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/Product'
 */
```

Let’s analyze it in parts, starting with the Swagger tags. A tag allows you to create a section within the Swagger docs. All the routes assigned to this tag will appear under the same division. It’s an organizational setting.

In our example, all the endpoints will be mapped to the same tag. Next, we set up our first route: Retrive Products. It’s pretty straightforward. First, define a title and specify the tag to which the path will be attached.

Then, we have the request and the response. Within the request, define three things: whether the request is required, the content type of the request, and the schema from which it must be processed.

The schemas can be referenced through the `#components/schemas ` Swagger operator. As for the response, define the HTTP response codes and the properties for each of them. We’re just worried about the happy path with an HTTP 200.

![routes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/scl3locbuiel0kekbt8x.png)
You can test the new operation directly within the Swagger UI page

Now, you can see where the example values take place. It’s easier to provide your users with sample data as a reference for when they want to perform stuff.

**Entire code of the Routes **

```
const express = require("express");
const router = express.Router();
const Product = require('../models/product');

/**
 * @swagger
 * components:
 *   schemas:
 *     Product:
 *       type: object
 *       required:
 *         - name
 *         - brand
 *         - ram
 *         - camera
 *         - network
 *         - fingerprint
 *         - price
 *       properties:
 *         _id:
 *           type: string
 *           description: The auto-generated unique identifier of the product.
 *         name:
 *           type: string
 *           description: The name of the product.
 *         brand:
 *           type: string
 *           description: The brand of the product.
 *         ram:
 *           type: integer
 *           description: The RAM capacity of the product in gigabytes.
 *         camera:
 *           type: string
 *           description: The camera specification of the product.
 *         network:
 *           type: string
 *           description: The network connectivity of the product.
 *         fingerprint:
 *           type: boolean
 *           description: Indicates whether the product has a fingerprint sensor.
 *         price:
 *           type: number
 *           description: The price of the product.
 *       example:
 *         _id: "65d34a7cbe5329a2e035c756"
 *         name: Galaxy S21
 *         brand: Samsung
 *         ram: 8
 *         camera: 64 MP
 *         network: 5G
 *         fingerprint: true
 *         price: 799
 */

/**
 * @swagger
 * /products:
 *   get:
 *     summary: Retrieve a list of all products.
 *     description: Retrieve a list of all products from the database.
 *     responses:
 *       200:
 *         description: A list of products.
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/Product'
 */
router.get('/', async (req, res) => {
  try {
    const products = await Product.find();
    res.json(products);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});

/**
 * @swagger
 * /products/{id}:
 *   get:
 *     summary: Retrieve a product by ID.
 *     description: Retrieve a product from the database by its ID.
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         description: ID of the product to retrieve.
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: The requested product.
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Product'
 *       404:
 *         description: Product not found.
 */
router.get('/:id', getProduct, (req, res) => {
  res.json(res.product);
});

/**
 * @swagger
 * /products:
 *   post:
 *     summary: Create a new product.
 *     description: Create a new product and save it to the database.
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/Product'
 *     responses:
 *       201:
 *         description: The created product.
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Product'
 *       400:
 *         description: Invalid product data.
 */
router.post('/', async (req, res) => {
  const product = new Product({
    name: req.body.name,
    brand: req.body.brand,
    ram: req.body.ram,
    camera: req.body.camera,
    network: req.body.network,
    fingerprint: req.body.fingerprint,
    price: req.body.price
  });

  try {
    const newProduct = await product.save();
    res.status(201).json(newProduct);
  } catch (error) {
    res.status(400).json({ message: error.message });
  }
});

/**
 * @swagger
 * /products/{id}:
 *   put:
 *     summary: Update a product by ID.
 *     description: Update an existing product in the database by its ID.
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         description: ID of the product to update.
 *         schema:
 *           type: string
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             $ref: '#/components/schemas/Product'
 *     responses:
 *       200:
 *         description: The updated product.
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Product'
 *       404:
 *         description: Product not found.
 */
router.put('/:id', getProduct, async (req, res) => {
  try {
    const updatedProduct = await res.product.set(req.body).save();
    res.json(updatedProduct);
  } catch (error) {
    res.status(400).json({ message: error.message });
  }
});

/**
 * @swagger
 * /products/{id}:
 *   delete:
 *     summary: Delete a product by ID.
 *     description: Delete a product from the database by its ID.
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         description: ID of the product to delete.
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: Product deleted successfully.
 *       404:
 *         description: Product not found.
 */
router.delete('/:id', getProduct, async (req, res) => {
  try {
    await res.product.remove();
    res.json({ message: 'Product deleted successfully' });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});

async function getProduct(req, res, next) {
  try {
    const product = await Product.findById(req.params.id);
    if (product == null) {
      return res.status(404).json({ message: 'Product not found' });
    }
    res.product = product;
    next();
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
}

module.exports = router;

```

![all routes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnvpwzmbc7shi1erfsz9.png)


## Conclusion
You may test each endpoint individually to ensure it’s working precisely as your Postman requests.

Swagger is capable of way more than merely documenting your APIs. A quick read over the official docs will give you a better understanding of its power. Remember that documenting should be part of your Project. 

![pic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksha7di9wp8r35hvgbn0.png)