OpenAPI & Documentation
A real OpenAPI spec snippet, why documenting contracts matters, and code-first vs spec-first docs.
Why documenting a contract matters
An API's real contract isn't the code that implements it — it's whatever its consumers actually believe it does. Without a formal, machine-readable specification, that contract only exists as tribal knowledge, scattered comments, and whatever a consumer managed to reverse-engineer from trial and error. OpenAPI (formerly known as Swagger) is the industry-standard format for describing an HTTP API's contract precisely: every endpoint, every parameter, every possible response shape and status code, in a structured YAML or JSON document that both humans and tools can read.
The payoff isn't just a nicer-looking docs page. A real OpenAPI spec is input to real tooling: generating a client SDK in another language, generating server-side request validation, driving contract tests against the actual implementation (see Testing REST APIs), and giving a new integrator a single, precise source of truth instead of a Slack thread.
A real OpenAPI spec snippet
Describing two endpoints on a small users API — enough structure to see how the pieces fit together:
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
description: Create and retrieve user accounts.
paths:
/users/{id}:
get:
summary: Get a user by ID
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: The user was found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: No user exists with this ID
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/users:
post:
summary: Create a new user
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, email]
properties:
name:
type: string
email:
type: string
format: email
responses:
'201':
description: The user was created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'422':
description: Validation failed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
Error:
type: object
properties:
error:
type: string
Every piece here is meaningful to tooling, not just a human reader: $ref lets User and Error be defined once and reused across every endpoint that needs them, the responses map documents every status code a client should actually handle (not just the happy path), and requestBody's schema is precise enough that a validator can reject a malformed request before it ever reaches application code.
Generating docs from code vs. spec-first
Two fundamentally different workflows produce the same kind of document, and teams genuinely disagree about which is better:
Code-first (docs generated from annotations): the spec is derived from comments/decorators already living next to the route handlers, using a tool like swagger-jsdoc:
/**
* @openapi
* /users/{id}:
* get:
* summary: Get a user by ID
* parameters:
* - name: id
* in: path
* required: true
* schema: { type: integer }
* responses:
* 200:
* description: The user was found
*/
app.get('/users/:id', async (req, res) => {
// ... route implementation
});
Spec-first: the YAML document is written before any implementation exists, and becomes the agreed contract multiple teams (frontend, backend, a third-party integrator) build against in parallel — server stubs and client SDKs can both be generated directly from it, before either side has written any real logic.
| Code-first | Spec-first | |
|---|---|---|
| Where the contract lives | Annotations next to the code | A standalone YAML/JSON file |
| Risk of drift | Lower — the spec is generated straight from the code that runs | Higher — nothing forces the implementation to actually match the spec unless something validates it |
| Good for | A single team owning both the API and its docs, wanting minimal extra process | Multiple teams (or a public API with external integrators) needing to agree on a contract before building against it |
| Typical tooling | swagger-jsdoc, decorators in frameworks like NestJS |
Stoplight, openapi-generator for stubs/clients, contract-testing tools |
Neither approach is strictly better — code-first minimizes the risk of the docs silently drifting from what the code actually does, since they're generated from the same source; spec-first is stronger when the spec itself needs to be a stable, reviewable artifact that multiple parties agree on and build against independently, sometimes before a single line of the implementation exists.
Serving interactive docs
Once a spec exists — hand-written or generated — Swagger UI renders it as an interactive page where anyone can read every endpoint's contract and fire off real test requests directly from the browser, with no separate Postman collection needed:
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
const openApiSpec = YAML.load('./openapi.yaml');
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openApiSpec));
Visiting /docs now gives every consumer of the API a live, browsable, always-current reference — provided the underlying spec is actually kept in sync with reality, which is the whole challenge this page keeps coming back to.
Common mistakes
- Writing an OpenAPI spec once at launch and never updating it as the API evolves — a stale spec is worse than no spec at all, since it actively misleads anyone who trusts it, whereas "no docs" at least prompts someone to go read the code.
- Documenting only the happy-path
200/201response and omitting error responses (404,422,429) from the spec — a consumer building error handling against an incomplete spec has no idea what failure shapes to actually expect. - Treating an OpenAPI spec as pure documentation instead of a validated contract — without something in CI that checks the real implementation's responses against the spec (see Testing REST APIs), nothing stops the two from silently diverging over time.
- Choosing spec-first for a single small team with no external consumers, adding process overhead neither the team nor its API actually needs — code-first with generated docs is usually the simpler, lower-friction choice in that situation.
Interview questions
Q: What's the practical benefit of an OpenAPI spec beyond generating a documentation page? It's a machine-readable contract that real tooling can act on — generating client SDKs in other languages, generating server-side request validation, and driving automated contract tests that check the real implementation still matches what the spec promises. A documentation page is just one consumer of that same underlying document.
Q: What's the core trade-off between generating API docs from code annotations versus writing the OpenAPI spec first? Code-first (generating the spec from annotations already living next to route handlers) keeps the spec less likely to drift from the real implementation, since both come from the same source. Spec-first treats the YAML document itself as the agreed contract, written before implementation exists, which is stronger when multiple teams or external integrators need to build against a stable, reviewable contract in parallel — at the cost of nothing automatically keeping the spec and the eventual implementation in sync unless something validates that separately.