OpenAPI is the industry-standard, machine-readable way to describe a REST API: every path, parameter, body, response and security scheme in one YAML or JSON document that can render documentation, validate requests and generate client SDKs. In this lesson you will learn the structure of an OpenAPI document, how to serve it with Swagger UI in Express, and how to keep it truthful.
Swagger was the original name of the specification and remains the name of the tooling (Swagger UI, Swagger Editor); the specification itself became OpenAPI in 2016. The current version, 3.1, uses plain JSON Schema for its schemas, so everything you know about JSON Schema validation applies directly.
openapi: 3.1.0
info:
title: Bookstore API
version: 1.2.0
paths:
/books:
get:
summary: List books
operationId: listBooks
parameters:
- name: genre
in: query
schema: { type: string }
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
responses:
"200":
description: A page of books
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Book" }
post:
summary: Create a book
operationId: createBook
security: [{ bearerAuth: [] }]
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/NewBook" }
responses:
"201":
description: Created
"422":
description: Validation failed
components:
securitySchemes:
bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT }
schemas:
NewBook:
type: object
required: [title, author]
properties:
title: { type: string, maxLength: 200 }
author: { type: string }
Book:
allOf:
- $ref: "#/components/schemas/NewBook"
- type: object
required: [id]
properties:
id: { type: string, examples: ["bk_42"] }paths map URL templates to operations keyed by HTTP method; each operation lists parameters (in: query, path or header), an optional requestBody, and responses keyed by status code. components holds reusable schemas and security schemes referenced with $ref, and operationId gives each operation a stable name that code generators use.
swagger-ui-express renders the document as an interactive page where readers can try requests live:
const fs = require("fs");
const YAML = require("yaml");
const swaggerUi = require("swagger-ui-express");
const spec = YAML.parse(fs.readFileSync("./openapi.yaml", "utf8"));
app.use("/docs", swaggerUi.serve, swaggerUi.setup(spec));
app.get("/openapi.json", (req, res) => res.json(spec));Publishing the raw document at /openapi.json matters as much as the page: it is what Postman imports, SDK generators read and gateways validate against.
| Approach | How | Strengths |
|---|---|---|
| Design-first | Write openapi.yaml before code and review it like a pull request | Contract agreed early; frontend and backend work in parallel |
| Code-first | Annotate routes (swagger-jsdoc comments, decorators in NestJS or FastAPI) and generate the document | Never drifts from the implementation |
Either way, make the document authoritative by validating traffic against it. express-openapi-validator rejects violating requests with a 400, so an undocumented parameter simply does not work:
const OpenApiValidator = require("express-openapi-validator");
app.use(OpenApiValidator.middleware({ apiSpec: "./openapi.yaml", validateRequests: true }));401, 404, 422, 429) with the Problem Details schema.openapi-typescript) so breaking changes surface as compile errors.In an OpenAPI document, where do reusable schemas such as `Book` live?
components and are referenced with $ref; operationId names each operation.swagger-ui-express serves interactive docs; also publish the raw document at /openapi.json.Next lesson: Automated API Testing with Jest and Supertest — turn the contract into a test suite that runs on every commit.