NodeJSDocumenting APIs (OpenAPI/Swagger)

Documenting APIs (OpenAPI/Swagger)

An undocumented API is one nobody can use without reading your source. OpenAPI (formerly Swagger) is the industry-standard, machine-readable format for describing a REST API — its endpoints, parameters, request/response shapes, and auth. From a single OpenAPI document you get interactive docs, client SDKs, server stubs, and contract tests. This page shows what an OpenAPI spec looks like and how to serve interactive docs from an Express app.

What OpenAPI gives you
  • Interactive docs — Swagger UI / Redoc let users explore and try endpoints in the browser.

  • Client SDK generation — generate typed clients for TypeScript, Python, Go… from the spec.

  • Server stubs & validation — validate requests/responses against the schema at runtime.

  • A single source of truth — the contract, versioned alongside your code.

OpenAPI is the spec; Swagger is the tooling around it
"Swagger" was the original name; the format was donated and renamed **OpenAPI** (current major version 3.x). The tools kept the Swagger brand: *Swagger UI* (interactive docs), *Swagger Editor*, *Swagger Codegen*. So you write an *OpenAPI* document and view it with *Swagger UI* — they're the same ecosystem.
A minimal OpenAPI document

openapi.js

JS
export const openapiSpec = {
  openapi: '3.0.3',
  info: { title: 'Tasks API', version: '1.0.0' },
  paths: {
    '/tasks': {
      get: {
        summary: 'List tasks',
        responses: {
          200: {
            description: 'An array of tasks',
            content: {
              'application/json': {
                schema: { type: 'array', items: { $ref: '#/components/schemas/Task' } },
              },
            },
          },
        },
      },
      post: {
        summary: 'Create a task',
        requestBody: {
          required: true,
          content: {
            'application/json': {
              schema: { $ref: '#/components/schemas/NewTask' },
            },
          },
        },
        responses: { 201: { description: 'Created' }, 422: { description: 'Validation failed' } },
      },
    },
  },
  components: {
    schemas: {
      Task: {
        type: 'object',
        properties: {
          id: { type: 'integer' },
          title: { type: 'string' },
          done: { type: 'boolean' },
        },
        required: ['id', 'title', 'done'],
      },
      NewTask: {
        type: 'object',
        properties: { title: { type: 'string', minLength: 1 } },
        required: ['title'],
      },
    },
  },
}
`$ref` keeps schemas DRY
Define each model once under `components/schemas` and reference it with `$ref` everywhere it appears. A `Task` returned by `GET`, `POST`, and `PATCH` is described in one place — change it once and every endpoint reflects it. This mirrors how you'd share a [validation schema](/nodejs/request-validation) in code; in fact, tools can *generate* OpenAPI from Zod/Joi schemas so the docs never drift from the validation.
Serving interactive docs in Express

JS
import swaggerUi from 'swagger-ui-express'
import { openapiSpec } from './openapi.js'

// Interactive docs at /docs:
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openapiSpec))

// Raw spec for codegen tools / other consumers:
app.get('/openapi.json', (req, res) => res.json(openapiSpec))
Open http://localhost:3000/docs
→ Swagger UI renders every endpoint with "Try it out" buttons,
  request/response schemas, and example payloads.
Generating the spec instead of hand-writing it

Approach

Tool

Idea

JSDoc comments

swagger-jsdoc

Annotate routes; build the spec from comments

From Zod schemas

zod-to-openapi

Reuse validation schemas as the doc source

Spec-first

Swagger Editor

Write the YAML first, generate stubs/clients

From types

tsoa, ts-rest

Derive routes + spec from TypeScript types

Hand-maintained docs rot — generate from the code where you can
Documentation written separately from the implementation drifts the moment someone changes a route and forgets the docs. The whole value of OpenAPI evaporates if the spec lies. Prefer approaches that **derive** the spec from the same artifacts you already maintain — validation schemas, route definitions, or TypeScript types — so the docs update with the code. Manual specs are fine to start, but plan to automate before they get stale.
Document what clients actually need
  • Every endpoint: method, path, summary, and parameters.

  • Request body schema with required fields and examples.

  • All response statuses — success and the error envelope.

  • Authentication scheme (bearer token, API key) under securitySchemes.

  • Pagination, filtering, and rate-limit behavior.

Examples are worth more than prose
A concrete request/response *example* communicates faster than paragraphs of description. OpenAPI's `example`/`examples` fields render directly in Swagger UI, so a developer sees real payloads, not just types. Always document the failure cases too — clients spend most of their effort handling errors, so show the exact `4xx` shapes they'll receive.
Next
Tie it all together into a checklist for production-quality APIs: [REST API Best Practices](/nodejs/rest-best-practices).