Building a RESTful API from Scratch with Node.js (Part 5)
APIs tutorialcreate an APIAPI development guideAPIs tutorial

5 min read

Adding API Documentation with Swagger to Your Node.js API

This tutorial builds on our rate-limited API by adding comprehensive API documentation using Swagger/OpenAPI. We'll implement interactive documentation that helps developers understand and test your API endpoints.

Prerequisites

  • Completed Parts 1-4 of the tutorial
  • Basic understanding of REST API concepts
  • Node.js and npm installed

Project Setup

Install the required dependencies:

npm install swagger-ui-express swagger-jsdoc yaml

Updated Project Structure

Add these new files to your project:

books-api/
├── src/
│   ├── config/
│   │   ├── database.js
│   │   ├── auth.js
│   │   ├── rateLimiter.js
│   │   └── swagger.js
│   ├── docs/
│   │   ├── swagger.yaml
│   │   ├── components.yaml
│   │   ├── paths/
│   │   │   ├── auth.yaml
│   │   │   └── books.yaml
│   │   └── schemas/
│   │       ├── Book.yaml
│   │       └── User.yaml
│   └── server.js

Environment Setup

Update your .env file:

API_VERSION=1.0.0
API_TITLE=Books API
API_DESCRIPTION=RESTful API for managing books
API_SERVER_URL=http://localhost:3000

Swagger Configuration

Create src/config/swagger.js:

const swaggerJsdoc = require('swagger-jsdoc');
const yaml = require('yaml');
const fs = require('fs');
const path = require('path');

// Read individual YAML files
const readYamlFile = (filePath) => {
    const fileContents = fs.readFileSync(filePath, 'utf8');
    return yaml.parse(fileContents);
};

// Swagger definition
const swaggerDefinition = {
    openapi: '3.0.0',
    info: {
        title: process.env.API_TITLE,
        version: process.env.API_VERSION,
        description: process.env.API_DESCRIPTION,
        license: {
            name: 'MIT',
            url: 'https://opensource.org/licenses/MIT',
        },
        contact: {
            name: 'API Support',
            email: 'support@example.com',
        },
    },
    servers: [
        {
            url: process.env.API_SERVER_URL,
            description: 'Development server',
        },
    ],
    components: {
        securitySchemes: {
            BearerAuth: {
                type: 'http',
                scheme: 'bearer',
                bearerFormat: 'JWT',
            },
        },
        schemas: {
            Book: readYamlFile(path.join(__dirname, '../docs/schemas/Book.yaml')),
            User: readYamlFile(path.join(__dirname, '../docs/schemas/User.yaml')),
        },
    },
};

// Swagger options
const options = {
    swaggerDefinition,
    apis: [
        './src/routes/*.js',
        './src/docs/paths/*.yaml'
    ],
};

const swaggerSpec = swaggerJsdoc(options);

module.exports = swaggerSpec;

Schema Definitions

Create src/docs/schemas/Book.yaml:

type: object
properties:
  _id:
    type: string
    format: objectId
    description: The auto-generated id of the book
  title:
    type: string
    description: The title of the book
  author:
    type: string
    description: The author of the book
  isbn:
    type: string
    description: ISBN number of the book
  publishedDate:
    type: string
    format: date
    description: Publication date of the book
  genre:
    type: string
    description: Genre of the book
  description:
    type: string
    description: Brief description of the book
required:
  - title
  - author
  - isbn

Create src/docs/schemas/User.yaml:

type: object
properties:
  _id:
    type: string
    format: objectId
    description: The auto-generated id of the user
  email:
    type: string
    format: email
    description: User's email address
  name:
    type: string
    description: User's full name
  role:
    type: string
    enum: [user, editor, admin]
    description: User's role in the system
  createdAt:
    type: string
    format: date-time
    description: Account creation timestamp
required:
  - email
  - name
  - role

API Path Documentation

Create src/docs/paths/books.yaml:

/api/books:
  get:
    tags:
      - Books
    summary: Get all books
    description: Retrieve a list of all books with optional filtering
    parameters:
      - in: query
        name: genre
        schema:
          type: string
        description: Filter books by genre
      - in: query
        name: author
        schema:
          type: string
        description: Filter books by author
    responses:
      200:
        description: Successful operation
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/Book'
      429:
        description: Too many requests
      500:
        description: Server error
  
  post:
    tags:
      - Books
    summary: Add a new book
    description: Add a new book to the database
    security:
      - BearerAuth: []
    requestBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Book'
    responses:
      201:
        description: Book created successfully
      400:
        description: Invalid input
      401:
        description: Unauthorized
      429:
        description: Too many requests

Create src/docs/paths/auth.yaml:

/api/auth/login:
  post:
    tags:
      - Authentication
    summary: User login
    description: Authenticate user and receive JWT token
    requestBody:
      required: true
      content:
        application/json:
          schema:
            type: object
            properties:
              email:
                type: string
                format: email
              password:
                type: string
                format: password
            required:
              - email
              - password
    responses:
      200:
        description: Login successful
        content:
          application/json:
            schema:
              type: object
              properties:
                token:
                  type: string
                user:
                  $ref: '#/components/schemas/User'
      401:
        description: Invalid credentials
      429:
        description: Too many login attempts

Route Documentation

Update your route files to include JSDoc comments. Example for src/routes/books.js:

const express = require('express');
const router = express.Router();

/**
 * @swagger
 * tags:
 *   name: Books
 *   description: Book management endpoints
 */

/**
 * @swagger
 * /api/books/{id}:
 *   get:
 *     tags: [Books]
 *     summary: Get a book by ID
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: string
 *         description: Book ID
 *     responses:
 *       200:
 *         description: Success
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/Book'
 *       404:
 *         description: Book not found
 */
router.get('/:id', getBook);

// Other routes...

Updated Server File

Update src/server.js:

const express = require('express');
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./config/swagger');
const { basicRateLimiter } = require('./middleware/rateLimiter');

const app = express();

// Swagger UI setup
app.use(
    '/api-docs',
    swaggerUi.serve,
    swaggerUi.setup(swaggerSpec, {
        explorer: true,
        customSiteTitle: 'Books API Documentation',
        customCss: '.swagger-ui .topbar { display: none }',
        swaggerOptions: {
            docExpansion: 'none',
            persistAuthorization: true,
            filter: true,
        },
    })
);

// Rate limit the documentation
app.use('/api-docs', basicRateLimiter);

// Existing middleware and routes...

Custom Documentation Customization

Create a custom documentation template:

const customOptions = {
    customCssUrl: '/custom-swagger.css',
    customJs: '/custom-swagger.js',
    customfavIcon: '/favicon.ico',
};

// Custom CSS for documentation
app.get('/custom-swagger.css', (req, res) => {
    res.sendFile(path.join(__dirname, 'docs/styles/swagger.css'));
});

Documentation Security

Implement documentation access control:

const docsAuthMiddleware = (req, res, next) => {
    const apiKey = req.headers['x-api-key'];
    if (process.env.NODE_ENV === 'production' && apiKey !== process.env.DOCS_API_KEY) {
        return res.status(401).json({ error: 'Unauthorized access to documentation' });
    }
    next();
};

app.use('/api-docs', docsAuthMiddleware);

Testing Documentation

Test your API documentation using these curl commands:

# Get documentation JSON
curl http://localhost:3000/api-docs.json

# Test documentation with API key
curl -H "X-API-KEY: your_api_key" http://localhost:3000/api-docs

Best Practices Implemented

  1. Organization

    • Modular documentation structure
    • Separate schema definitions
    • Route-specific documentation files
    • Environment-based configuration
  2. Security

    • Protected documentation access
    • Rate-limited documentation endpoints
    • Environment-specific settings
    • Authentication examples
  3. User Experience

    • Interactive API testing
    • Clear parameter descriptions
    • Request/response examples
    • Error documentation
  4. Maintenance

    • Version control
    • Easy updates
    • Consistent formatting
    • Reusable components

Next Steps

To enhance your API further:

  1. Setting up automated testing
  2. Adding pagination for GET requests
  3. Implementing proper logging

Conclusion

You now have comprehensive API documentation that:

  • Helps developers understand your API
  • Provides interactive testing
  • Maintains security
  • Scales with your API
  • Follows OpenAPI standards

Remember to:

  • Keep documentation updated
  • Test documentation regularly
  • Gather user feedback
  • Monitor documentation usage
  • Update examples as needed

The next tutorial will cover implementing automated testing for your API endpoints.

Related Posts

API Management Tools: A Comprehensive Overview
APIs tutorialcreate an APIAPI development guideAPIs tutorial

5 min read

APIs (Application Programming Interfaces) are the backbone of modern digital applications. They allow different software systems to communicate, exchange data, and collaborate seamlessly. As businesse...

API Security: Best Practices for Protecting Your Application
APIs tutorialcreate an APIAPI development guideAPIs tutorial

4 min read

In today’s interconnected digital world, APIs (Application Programming Interfaces) are the backbone of communication between different software applications. From mobile apps to cloud services, APIs e...

API Design Best Practices: Crafting Robust and Scalable APIs
APIs tutorialcreate an APIAPI development guideAPIs tutorial

5 min read

In the modern digital ecosystem, APIs (Application Programming Interfaces) serve as the backbone of connectivity. Whether you're building microservices, enabling integrations, or crafting data pipelin...