Generating OpenAPI documentation for your Express.js routes using Swagger might seem like a chore, but it’s actually the most reliable way to ensure your API consumers know exactly what to expect, and it turns out the tools do most of the heavy lifting.

Let’s see this in action. Imagine a simple Express app with a couple of routes:

const express = require('express');
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json'); // We'll create this

const app = express();
const port = 3000;

app.use(express.json());

// GET /users
app.get('/users', (req, res) => {
  // In a real app, you'd fetch from a database
  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];
  res.json(users);
});

// POST /users
app.post('/users', (req, res) => {
  const newUser = req.body;
  // In a real app, you'd save this to a database
  newUser.id = Date.now(); // Assign a temporary ID
  res.status(201).json(newUser);
});

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
  console.log(`API Docs available at http://localhost:${port}/api-docs`);
});

Now, the magic happens in swagger.json. This file defines your API’s structure, endpoints, request/response schemas, and more.

{
  "openapi": "3.0.0",
  "info": {
    "title": "User API",
    "version": "1.0.0",
    "description": "API for managing users"
  },
  "servers": [
    {
      "url": "http://localhost:3000"
    }
  ],
  "paths": {
    "/users": {
      "get": {
        "summary": "Get a list of users",
        "responses": {
          "200": {
            "description": "A list of users",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/User"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a new user",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewUserInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Unique identifier for the user"
          },
          "name": {
            "type": "string",
            "description": "Name of the user"
          }
        },
        "required": ["id", "name"]
      },
      "NewUserInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Name of the new user"
          }
        },
        "required": ["name"]
      }
    }
  }
}

When you run this app (node your_app.js) and navigate to http://localhost:3000/api-docs, you’ll see a beautifully rendered, interactive API documentation page. This page allows you to explore all your endpoints, see their request and response formats, and even try them out directly from the browser.

The core problem this solves is API discoverability and maintainability. Without a clear contract, developers consuming your API are left guessing about data formats, expected parameters, and potential error responses. OpenAPI (formerly Swagger) provides that contract. It’s a machine-readable format that can be used by various tools to generate client SDKs, server stubs, and, of course, interactive documentation.

Internally, swagger-ui-express simply takes your OpenAPI definition (the swagger.json in this case) and renders it using the Swagger UI library. The swagger-ui library is a JavaScript application that reads the OpenAPI specification and dynamically generates HTML, CSS, and JavaScript to present the API documentation in a user-friendly way. The swaggerUi.serve middleware tells Express to serve the static assets of Swagger UI, and swaggerUi.setup(swaggerDocument) injects your specific API definition into that UI.

The real power comes from generating this swagger.json file automatically from your code. Libraries like swagger-jsdoc or swagger-autogen can parse JSDoc comments in your route handlers and convert them into an OpenAPI specification. This keeps your documentation in sync with your code with minimal manual effort. For example, you could add comments like this above your GET /users route:

/**
 * @openapi
 * /users:
 *   get:
 *     summary: Get a list of users
 *     responses:
 *       200:
 *         description: A list of users
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/User'
 */
app.get('/users', (req, res) => { ... });

Then, a tool like swagger-jsdoc would parse this and merge it into your main swaggerDocument object.

When you’re defining your schemas, especially for complex nested objects or polymorphic data, using $ref to point to definitions within #/components/schemas is crucial. This promotes reusability and keeps your OpenAPI document DRY (Don’t Repeat Yourself). For instance, instead of defining the User object structure in every endpoint response where it appears, you define it once under components.schemas and then reference it using "$ref": "#/components/schemas/User". This is not just for readability; it’s how you ensure consistency across your API. If you need to add a new field to the User object, you only change it in one place, and that change is reflected everywhere the User schema is referenced.

The next step after getting your OpenAPI docs serving is to leverage them for automated testing or client generation.

Want structured learning?

Take the full Express course →