Easy way to Integrate Swagger with Node.js REST APIs

Published: (December 11, 2025 at 07:45 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Swagger is one of the simplest ways to document REST APIs. It provides an interactive UI where developers can test endpoints, understand inputs and outputs, and integrate your API faster.

This guide covers the exact steps used to integrate Swagger into a large Node.js backend and the fixes required to make Swagger UI work correctly in both development and production.

1. Install Swagger Libraries

Only two packages are required.

npm install swagger-jsdoc swagger-ui-express

2. Create Swagger Configuration File

Create docs/swagger.config.js.

import swaggerJSDoc from "swagger-jsdoc";

const swaggerDefinition = {
  openapi: "3.0.0",
  info: {
    title: "Dining System API",
    version: "1.0.0",
    description: "API documentation for Dining System",
  },
  servers: [
    { url: process.env.DEV, description: "Development" },
    { url: process.env.PROD, description: "Production" },
  ],
  components: {
    securitySchemes: {
      bearerAuth: {
        type: "http",
        scheme: "bearer",
        bearerFormat: "JWT",
      },
    },
  },
  tags: [
    { name: "Authentication" },
    { name: "Users" },
    { name: "Vendors" },
    { name: "Bills" },
    { name: "Meal" },
  ],
};

const options = {
  swaggerDefinition,
  apis: ["./docs/routes/*.js"], // Load swagger route docs
};

export default swaggerJSDoc(options);

3. Integrate Swagger UI into app.js

Import Swagger UI and the spec:

import swaggerUi from "swagger-ui-express";
import swaggerSpec from "./docs/swagger.config.js";

Add the Swagger UI route:

const swaggerUiOptions = {
  customCss: ".swagger-ui .topbar { display: none }",
  customSiteTitle: "Dining System API Docs",
  swaggerOptions: {
    persistAuthorization: true,
    displayRequestDuration: true,
    filter: true,
  },
};

app.use(
  "/api-docs",
  swaggerUi.serve,
  swaggerUi.setup(swaggerSpec, swaggerUiOptions)
);

app.get("/api-docs.json", (req, res) => {
  res.setHeader("Content-Type", "application/json");
  res.send(swaggerSpec);
});

4. app.js Reference

import express from "express";
import cors from "cors";
import helmet from "helmet";
import cookieParser from "cookie-parser";
import authRoutes from "./routes/authRoutes.js";
import swaggerUi from "swagger-ui-express";
import swaggerSpec from "./docs/swagger.config.js";
import rateLimit from "express-rate-limit";
import dotenv from "dotenv";

dotenv.config();

const app = express();

// Swagger should load before Helmet
app.use(
  "/api-docs",
  swaggerUi.serve,
  swaggerUi.setup(swaggerSpec, {
    customCss: ".swagger-ui .topbar { display: none }",
    swaggerOptions: { persistAuthorization: true },
  })
);

// JSON endpoint for tools
app.get("/api-docs.json", (req, res) => {
  res.setHeader("Content-Type", "application/json");
  res.send(swaggerSpec);
});

// Helmet applied after Swagger
app.use(
  helmet({
    contentSecurityPolicy: false,
  })
);

app.use(cors());
app.use(express.json());
app.use(cookieParser());

const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 300,
  message: { message: "Too many requests, please try again later." },
});
app.use(globalLimiter);

app.get("/", (req, res) => {
  res.send("Dining System API is running...");
});

app.use("/api/auth", authRoutes);
// ... other routes

app.use((err, req, res, next) => {
  console.error("Uncaught Error:", err);
  const payload = { message: err.message || "Internal Server Error" };
  if (process.env.NODE_ENV !== "production") payload.stack = err.stack;
  res.status(err.status || 500).json(payload);
});

export default app;

Why Swagger must be loaded before Helmet

Helmet blocks inline scripts, which Swagger UI relies on. Loading Swagger first avoids blank UI issues in production.

5. Create Separate Swagger Files for Each API Module

Instead of mixing Swagger comments inside Express route files, create a dedicated folder:

docs/routes/

Example structure:

docs/routes/auth.swagger.js
docs/routes/users.swagger.js
docs/routes/vendors.swagger.js
docs/routes/bills.swagger.js
docs/routes/meal.swagger.js

Example Swagger file (auth.swagger.js):

/**
 * @swagger
 * /api/account-types/list:
 *   get:
 *     summary: Get all account types
 *     tags: [Account Types]
 *     security: [{ bearerAuth: [] }]
 *     responses:
 *       200:
 *         description: OK
 */

Swagger automatically loads these files through the apis configuration:

apis: ["./docs/routes/*.js"]

6. Restart and Test Swagger

Development

npm run dev

Open Swagger UI:

http://localhost:5000/api-docs

Production (PM2)

git pull
npm install
pm2 restart all

Open Swagger UI:

http://your-production-url/api-docs

7. Issues Faced and How They Were Solved

1. Swagger UI blank in production

  • Cause: Helmet blocks inline scripts.
  • Fix: Disable CSP for Swagger as shown above (contentSecurityPolicy: false).

2. Swagger did not update after changes

  • Cause: PM2 was running an old build.
  • Fix: Restart the server.
pm2 restart all

3. Swagger route files were not loading

  • Cause: Wrong folder path in the configuration.
  • Fix: Use the correct path.
apis: ["./docs/routes/*.js"]
docs/
  swagger.config.js
  routes/
    auth.swagger.js
    users.swagger.js
    vendors.swagger.js
    bills.swagger.js
    meal.swagger.js

routes/
controllers/
models/
app.js
server.js
Back to Blog

Related posts

Read more »

Create Your First MCP App

TL;DR MCP Apps bring interactive UIs to conversational agents and other MCP clients. This tutorial shows how to create a simple yet powerful app source code he...

Experimental Hono auth npm package

What I’m Building I’m creating an auth package that developers can drop into their app without writing the usual boilerplate login, register, JWT, email verifi...