|$ curl https://forge-ai.dev/api/markdown?path=docs/backend/openapi
$cat docs/openapi.md
updated Recently·22 min read·published

OpenAPI

BackendAPIIntermediate🎯Free Tools
Introduction

OpenAPI is the standard language for describing HTTP APIs. A single OpenAPI document captures your endpoints, request parameters, request bodies, response shapes, authentication schemes, and error formats. That document becomes the foundation for documentation, client generation, server stubs, mock servers, and contract tests.

The current version of the specification is OpenAPI 3.1, which aligns with JSON Schema 2020-12. Earlier 3.0.x versions are still widely used and supported by most tooling. This guide focuses on patterns that work in both versions unless noted otherwise.

This guide covers the structure of an OpenAPI document, how to generate it from code, how to render interactive documentation, how to generate typed clients, and how to keep the specification and implementation in sync.

Specification Structure

An OpenAPI document has four major sections. The info block describes metadata such as title, version, and contact details. The paths block defines every endpoint. The components block holds reusable schemas, parameters, and security schemes. The security block applies default authentication requirements.

openapi-structure.yaml
YAML
1openapi: 3.0.3
2info:
3 title: ForgeLearn Orders API
4 version: 1.2.0
5 description: |
6 Manage orders, customers, and products for the ForgeLearn platform.
7 contact:
8 name: API Support
9 email: api@forgelearn.dev
10 license:
11 name: MIT
12 url: https://opensource.org/licenses/MIT
13
14servers:
15 - url: https://api.forgelearn.dev/v1
16 description: Production
17 - url: https://api-staging.forgelearn.dev/v1
18 description: Staging
19
20paths:
21 /orders:
22 get:
23 operationId: listOrders
24 summary: List orders
25 tags: [Orders]
26 parameters:
27 - $ref: "#/components/parameters/PageCursor"
28 - $ref: "#/components/parameters/PageLimit"
29 responses:
30 "200":
31 $ref: "#/components/responses/OrderListResponse"
32 post:
33 operationId: createOrder
34 summary: Create an order
35 tags: [Orders]
36 requestBody:
37 required: true
38 content:
39 application/json:
40 schema:
41 $ref: "#/components/schemas/CreateOrderRequest"
42 responses:
43 "201":
44 $ref: "#/components/responses/OrderResponse"
45 "422":
46 $ref: "#/components/responses/ValidationError"
47
48components:
49 schemas:
50 Order:
51 type: object
52 required: [id, customer_id, status, total]
53 properties:
54 id:
55 type: string
56 format: uuid
57 customer_id:
58 type: string
59 format: uuid
60 status:
61 type: string
62 enum: [pending, paid, shipped, cancelled]
63 total:
64 type: integer
65 description: Amount in smallest currency unit
66 created_at:
67 type: string
68 format: date-time
69
70 parameters:
71 PageCursor:
72 name: cursor
73 in: query
74 schema:
75 type: string
76 PageLimit:
77 name: limit
78 in: query
79 schema:
80 type: integer
81 minimum: 1
82 maximum: 100
83 default: 20
84
85 responses:
86 OrderListResponse:
87 description: Paginated list of orders
88 content:
89 application/json:
90 schema:
91 type: object
92 required: [data]
93 properties:
94 data:
95 type: array
96 items:
97 $ref: "#/components/schemas/Order"
98 next_cursor:
99 type: string
100 nullable: true
101
102 securitySchemes:
103 bearerAuth:
104 type: http
105 scheme: bearer
106 bearerFormat: JWT
107
108security:
109 - bearerAuth: []

best practice

Centralize reusable schemas in components.schemas and reference them with $ref. Duplicated inline schemas make the document hard to maintain and break code generation.
Generating from Code

The most reliable way to keep documentation current is to generate the OpenAPI document from your server code. Frameworks like FastAPI, NestJS, Hono, and Express with libraries such as Zodios or tsoa can emit a spec from your route definitions and validation schemas.

openapi-from-code.ts
TypeScript
1import { Hono } from "hono";
2import { z } from "zod";
3import { zValidator } from "@hono/zod-validator";
4import { openAPISpecs } from "@hono/zod-openapi";
5
6const CreateOrderSchema = z.object({
7 customerId: z.string().uuid(),
8 items: z.array(z.object({
9 productId: z.string().uuid(),
10 quantity: z.number().int().min(1),
11 })).min(1),
12});
13
14const OrderSchema = z.object({
15 id: z.string().uuid(),
16 customerId: z.string().uuid(),
17 status: z.enum(["pending", "paid", "shipped", "cancelled"]),
18 total: z.number().int(),
19 createdAt: z.string().datetime(),
20});
21
22const app = new Hono();
23
24app.post("/orders", zValidator("json", CreateOrderSchema), async (c) => {
25 const body = c.req.valid("json");
26 const order = await orderService.create(body);
27 return c.json(order, 201);
28});
29
30app.get("/openapi.json", async (c) => {
31 const spec = openAPISpecs(app, {
32 documentation: {
33 info: { title: "ForgeLearn API", version: "1.0.0" },
34 servers: [{ url: "https://api.forgelearn.dev" }],
35 },
36 });
37 return c.json(spec);
38});

info

Generate the spec as part of your build or CI pipeline. Committing a generated openapi.json lets documentation sites and client generators reference a stable artifact.
Documentation UIs

OpenAPI documents can be rendered into interactive documentation by tools such as Swagger UI, Redoc, and Scalar. These UIs let developers explore endpoints, read schema definitions, and send test requests directly from the browser.

ToolStrengthsBest For
Swagger UITry-it-now, widely knownInternal dashboards
RedocClean, readable layoutPublic developer portals
ScalarModern UX, API clientModern product docs
📝

note

Host your documentation UI at a stable URL such as /docs or /api/docs. Link to it from your developer portal and README.
Client SDK Generation

OpenAPI enables typed client generation for many languages. Tools like OpenAPI Generator, Kiota, and Orval take a spec and produce HTTP clients with typed request and response models. Generated clients reduce boilerplate and catch contract drift at compile time.

generate-client.sh
Bash
1# Generate a TypeScript client with openapi-generator
2npx @openapitools/openapi-generator-cli generate \
3 -i openapi.json \
4 -g typescript-fetch \
5 -o ./src/generated/api \
6 --additional-properties=supportsES6=true,npmName=@forgelearn/api-client
7
8# Or use Orval for React Query / SWR hooks
9npx orval --config ./orval.config.js

Treat generated code as a build artifact. Do not hand-edit generated files. Regenerate them in CI when the spec changes and commit the result so downstream projects can install a specific version.

warning

Generated clients are only as good as the spec. Incomplete schemas, missing examples, and vague error responses produce awkward generated types. Invest in the spec quality first.
Contract Testing

Contract testing verifies that the implementation matches the specification. Schemathesis, Dredd, and Prism can read an OpenAPI document and send requests to your server to check that responses conform to the documented schemas and status codes.

contract-testing.sh
Bash
1# Validate server responses against openapi.json with schemathesis
2st run openapi.json --base-url=http://localhost:3000 --checks=all
3
4# Start a mock server from the spec with Prism
5npx @stoplight/prism-cli mock openapi.json
6
7# Use Prism as a validation proxy against a real server
8npx @stoplight/prism-cli proxy openapi.json http://localhost:3000 --errors

best practice

Run contract tests in CI before deploying. They catch mismatches between code and documentation that unit tests often miss, especially around optional fields and response codes.
Versioning and Evolution

APIs evolve, and the OpenAPI document should evolve with them. Additive changes such as new optional fields or new endpoints do not require a new version. Breaking changes such as removing fields, changing types, or removing endpoints should be published under a new version.

Use the deprecated: true flag on endpoints and fields you plan to remove. Include sunset headers in responses to give consumers advance notice. Document migration paths in the description fields.

openapi-versioning.yaml
YAML
1/legacy/reports:
2 get:
3 operationId: listLegacyReports
4 deprecated: true
5 description: |
6 This endpoint is deprecated and will be removed on 2027-01-01.
7 Use `/v2/reports` instead.
8 responses:
9 "200":
10 description: Legacy report list
11 headers:
12 Sunset:
13 schema:
14 type: string
15 description: Date after which the endpoint will be removed.

info

Keep a changelog in your info.description or a separate CHANGELOG.md. API consumers need to know what changed, why, and how to migrate.
$Blueprint — Engineering Documentation·Section ID: BE-05·Revision: 1.0