← Recipes

阅读中文版本

Generate OpenAPI Spec from Existing Routes

Walk an Express or Fastify route table, infer parameter and response types from handlers and validation schemas, and emit an OpenAPI 3.1 document for clients and docs.

~35 minopenapidocumentationnode

Task

Produce an OpenAPI 3.1 spec that matches the live API by introspecting the route table and the validation schemas already in the code.

When to use this

The API has 40 routes but the OpenAPI spec on the docs site is six months out of date. Frontend engineers keep asking which endpoints exist. The team wants the spec generated from the code so it cannot drift.

Prerequisites

  • A Node.js app with Express 4 or Fastify 4
  • A validation schema library (zod, joi, or ajv) attached to each route
  • The @asteasolutions/zod-to-openapi package or equivalent

Steps

1. Register an OpenAPI registry and map each route to its zod schema for params, query, and body.

registry.registerPath({
  method: "post",
  path: "/users",
  request: { body: z.object({ email: z.string().email() }) },
  responses: { 201: z.object({ id: z.string() }) },
});

Expected output: The registry holds one path entry per route, with schemas attached for every documented parameter.

2. Emit the OpenAPI document from the registry and write it to a file.

node tools/gen-openapi.mjs > openapi.json

Expected output: An openapi.json file with openapi 3.1.0, a paths object covering every registered route, and components.schemas for reused types.

3. Validate the generated spec with a linter that catches dangling references and invalid schemas.

npx @redocly/cli lint openapi.json

Expected output: The linter reports zero errors and zero warnings, confirming the spec is valid and self-consistent.

Troubleshooting

Problem

A route has no schema and is missing from the spec

Solution

Either attach a zod schema to the route or register it with a manual description; undocumented routes should fail the build.

Problem

The spec references a schema that is not in components

Solution

Register the shared schema with registry.registerComponent before referencing it, so zod-to-openapi emits it once.

Limitations

  • Inference cannot capture response status codes that depend on runtime logic; document those manually.
  • A route that uses dynamic regex paths may not map cleanly to an OpenAPI path template.

The spec should follow the code

A hand-maintained OpenAPI file always drifts from the routes. Generate it from the route table and the validation schemas that already exist, so the spec changes only when the code changes.

registry.registerPath({
  method: 'post',
  path: '/users',
  request: { body: z.object({ email: z.string().email() }) },
  responses: { 201: z.object({ id: z.string() }) },
});

Lint the output

A generated spec can still have dangling references or invalid schemas. Run a linter in CI and fail the build on any error. Treat undocumented routes as errors too, so a new endpoint cannot ship without a schema.

npx @redocly/cli lint openapi.json

Expected outcome

openapi.json covers every route, the linter passes clean, and the docs site regenerates from the spec on every build. Frontend engineers read the spec instead of asking which endpoints exist.