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:

YAML
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:

Javascript
/**
 * @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:

Javascript
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/201 response 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.