DEV Community

Vinay
Vinay

Posted on

Easy way to Integrate Swagger with Node.js REST APIs

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 we 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.

Install them:

npm install swagger-jsdoc swagger-ui-express
Enter fullscreen mode Exit fullscreen mode

2. Create Swagger Configuration File

Create:

docs/swagger.config.js
Enter fullscreen mode Exit fullscreen mode

This file defines:

  • OpenAPI version
  • API title and description
  • Server URLs (DEV and PROD)
  • Security schemes
  • Tags
  • Paths to your swagger route files

Example:

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);
Enter fullscreen mode Exit fullscreen mode

3. Integrate Swagger UI into app.js

Import Swagger UI:

import swaggerUi from "swagger-ui-express";
import swaggerSpec from "./docs/swagger.config.js";
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Why Swagger must be loaded before Helmet

Helmet blocks inline scripts, which Swagger UI uses.
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:

docs/routes/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Example swagger file:

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

Swagger automatically loads these files through:

apis: ["./docs/routes/*.js"]
Enter fullscreen mode Exit fullscreen mode

6. Restart and Test Swagger

Development

npm run dev
Enter fullscreen mode Exit fullscreen mode

Open Swagger:

http://localhost:5000/api-docs
Enter fullscreen mode Exit fullscreen mode

Production (PM2)

git pull
npm install
pm2 restart all
Enter fullscreen mode Exit fullscreen mode

Open:

http://your-production-url/api-docs
Enter fullscreen mode Exit fullscreen mode

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


2. Swagger did not update after changes

Cause: PM2 running old build
Fix: restart server

pm2 restart all
Enter fullscreen mode Exit fullscreen mode

3. Swagger route files were not loading

Cause: Wrong folder path
Fix: Use correct config

apis: ["./docs/routes/*.js"]
Enter fullscreen mode Exit fullscreen mode

8. Recommended Folder Structure

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
Enter fullscreen mode Exit fullscreen mode

Thank You

Top comments (0)