OpenAPI & Swagger
OpenAPI 3.x describes your HTTP API: paths, schemas, auth, and errors. Swagger UI renders that spec for humans. Node teams generate specs code-first (Zod, decorators) or write spec-first YAML then implement.
Search across all documentation pages
OpenAPI 3.x describes your HTTP API: paths, schemas, auth, and errors. Swagger UI renders that spec for humans. Node teams generate specs code-first (Zod, decorators) or write spec-first YAML then implement.
Quick-reference recipe card - copy-paste ready.
// Code-first with @asteasolutions/zod-to-openapi (pattern)
import { OpenAPIRegistry, OpenApiGeneratorV3 } from "@asteasolutions/zod-to-openapi";
import { z } from "zod";
const registry = new OpenAPIRegistry();
const OrderSchema = z.object({ id: z.string(), status: z.string() }).openapi("Order");
registry.registerPath({
method: "post",
path: "/v1/orders",
responses: { 201: { description: "Created", content: { "application/json": { schema: OrderSchema } } } },
});
const doc = new OpenApiGeneratorV3(registry.definitions).generateDocument({
openapi: "3.0.3",
info: { title: "Orders API", version: "1.0.0" },
});# Serve UI in dev only
npm install swagger-ui-expressWhen to reach for this:
// src/openapi/document.ts
import { z } from "zod";
import {
OpenAPIRegistry,
OpenApiGeneratorV3,
extendZodWithOpenApi,
} from "@asteasolutions/zod-to-openapi";
extendZodWithOpenApi(z);
const registry = new OpenAPIRegistry();
export const CreateOrderBody = z
.object({
customerId: z.string().uuid(),
sku: z.string(),
qty: z.number().int().positive(),
})
.openapi("CreateOrderBody");
export const Order = z
.object({
id: z.string(),
customerId: z.string().uuid(),
sku: z.string(),
qty: z.number(),
status: z.enum(["pending", "shipped"]),
})
.openapi("Order");
registry.registerPath({
method: "post",
path: "/v1/orders",
tags: ["Orders"],
request: { body: { content: { "application/json": { schema: CreateOrderBody } } } },
responses: {
201: {
description: "Order created",
content: { "application/json": { schema: z.object({ data: Order }) } },
},
400: { description: "Validation error" },
},
});
export function generateOpenApiDocument() {
return new OpenApiGeneratorV3(registry.definitions).generateDocument({
openapi: "3.0.3",
info: { title: "Acme Orders API", version: "1.0.0" },
servers: [{ url: "https://api.acme.example" }],
});
}
// src/main.ts
import express from "express";
import swaggerUi from "swagger-ui-express";
import { generateOpenApiDocument } from "./openapi/document";
const app = express();
const spec = generateOpenApiDocument();
if (process.env.NODE_ENV !== "production") {
app.use("/docs", swaggerUi.serve, swaggerUi.setup(spec));
}
app.get("/openapi.json", (_req, res) => res.json(spec));What this demonstrates:
/openapi.json serves machine-readable spec/v1openapi.yaml, implement routes to match, CI validates responses against spec@ApiProperty) or Zod generate YAML/JSON at build timeopenapi-generator or orval for TypeScript clients| Tool | Framework fit |
|---|---|
@asteasolutions/zod-to-openapi | Express/Fastify + Zod |
@fastify/swagger | Fastify 5 native |
@nestjs/swagger | NestJS 11 decorators |
tsoa | Express controllers as classes |
1. PR updates openapi/v1.yaml
2. Reviewers comment on contract
3. Implement handlers + contract tests
4. CI: spectral lint + breaking-change diff (oasdiff)// Share types with frontend via generated client - not raw OpenAPI dump
import type { components } from "./generated/api";
type Order = components["schemas"]["Order"];example on schemas.$ref split files per domain module.| Alternative | Use When | Don't Use When |
|---|---|---|
| Postman collections | Small internal APIs | Need codegen and breaking-change CI |
| GraphQL schema | Clients need flexible queries | Public REST B2B contract required |
| README only | Private scripts | External integrators |
| protobuf/gRPC | Internal high-throughput | Browser clients need REST |
3.0.3 has widest tooling support. 3.1 aligns JSON Schema closer - check generator compatibility.
Yes. Same Zod schema for runtime validation and spec generation prevents drift.
Register only public paths in OpenAPI registry; internal admin routes undocumented or separate spec.
@fastify/swagger + @fastify/swagger-ui with JSON Schema from route schema - idiomatic for Fastify 5.
SwaggerModule.setup in bootstrap; use DTO decorators. Export spec in CI artifact.
oasdiff breaking or openapi-diff between main and PR spec artifacts.
OpenAPI 3.1+ webhook objects or document as async callbacks section with payload schemas.
Document bearerAuth security scheme; match middleware behavior.
info.version is API release; URL /v1 is contract version. Bump URL on breaking changes.
Some tools introspect routes - fragile. Prefer build-time generation from schemas.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 16, 2026