Contracts and security
Describe the API with OpenAPI
Write a machine-readable contract for routes, parameters, request bodies, responses, and reusable schemas.
The behavior table from the first lesson lives in a text file only we can read. OpenAPI is the same table in a format tools can read: a YAML or JSON document that lists every path, parameter, request body, response and schema. From it you get rendered docs, generated clients, request validation and a way to catch drift in review. I wrote a longer walkthrough in the OpenAPI tutorial.
Start from what exists
Don’t describe the API you wish you had. Describe the routes that run today, then keep the document and the code moving together. Here is the collection route:
openapi: 3.1.0
info:
title: Books API
version: 1.0.0
paths:
/books:
get:
parameters:
- name: author
in: query
schema: { type: string }
responses:
'200':
description: A page of books
content:
application/json:
schema:
type: object
required: [books]
properties:
books:
type: array
items: { $ref: '#/components/schemas/Book' }
components:
schemas:
Book:
type: object
required: [id, title, author]
properties:
id: { type: string }
title: { type: string }
author: { type: string }
publishedYear: { type: integer }
Every response gets a status and a media type. For POST /books that means a 201 with application/json, a 422 with application/problem+json, and the Location header listed under headers. If a status can happen, it goes in the document.
Describe the wire, not the code
The document describes bytes on the network, not your TypeScript types. That distinction shows up in a few places.
required lists the properties that are always present, and publishedYear isn’t one of them. A property that can be null is different from a property that can be missing, so say which one you mean. A query parameter has in: query, a path parameter has in: path, and they’re never interchangeable.
Reuse schemas with $ref, but don’t merge things that differ. The Book a client receives has an id. The body a client sends must not. So define BookInput as a second schema instead of marking id optional on Book and hoping nobody sends one. Examples are nice for readers, but they constrain nothing. Only the schema does.
Keep it honest
An OpenAPI file that drifts from the server is worse than no file, because people trust it. Three habits keep it honest.
Commit the document next to the code, so a route change and its description land in the same pull request. Validate it in CI with a current parser against the openapi version it declares, so a typo fails the build. And write contract tests: send real requests through app.request() and check the responses against the document, including the Location header and the problem media type.
Generated clients are a good review tool. Generate one and read it as a consumer would. A field that turned optional when it shouldn’t be, or a status the client doesn’t know how to handle, means the document and the server have disagreed somewhere.
Describe GET and POST on /books now, with the Book and BookInput schemas and the 200, 201 and 422 responses. Render it with any OpenAPI viewer and compare it with the curl output from the first module.
Lesson completed