Good API documentation is the difference between developers loving or abandoning your API. OpenAPI provides a standard way to describe REST APIs.
OpenAPI Specification Basics#
1# openapi.yaml
2openapi: 3.1.0
3info:
4 title: My API
5 description: API for managing users and posts
6 version: 1.0.0
7 contact:
8 email: api@example.com
9 license:
10 name: MIT
11
12servers:
13 - url: https://api.example.com/v1
14 description: Production
15 - url: https://staging-api.example.com/v1
16 description: Staging
17
18tags:
19 - name: Users
20 description: User management
21 - name: Posts
22 description: Blog post operationsDefining Endpoints#
1paths:
2 /users:
3 get:
4 tags:
5 - Users
6 summary: List all users
7 description: Returns a paginated list of users
8 operationId: listUsers
9 parameters:
10 - name: page
11 in: query
12 description: Page number
13 schema:
14 type: integer
15 default: 1
16 minimum: 1
17 - name: limit
18 in: query
19 description: Items per page
20 schema:
21 type: integer
22 default: 20
23 minimum: 1
24 maximum: 100
25 responses:
26 '200':
27 description: Successful response
28 content:
29 application/json:
30 schema:
31 $ref: '#/components/schemas/UserList'
32 '401':
33 $ref: '#/components/responses/Unauthorized'
34
35 post:
36 tags:
37 - Users
38 summary: Create a new user
39 operationId: createUser
40 requestBody:
41 required: true
42 content:
43 application/json:
44 schema:
45 $ref: '#/components/schemas/CreateUserRequest'
46 responses:
47 '201':
48 description: User created
49 content:
50 application/json:
51 schema:
52 $ref: '#/components/schemas/User'
53 '400':
54 $ref: '#/components/responses/BadRequest'
55 '409':
56 description: Email already exists
57
58 /users/{id}:
59 get:
60 tags:
61 - Users
62 summary: Get user by ID
63 operationId: getUser
64 parameters:
65 - $ref: '#/components/parameters/UserId'
66 responses:
67 '200':
68 description: User found
69 content:
70 application/json:
71 schema:
72 $ref: '#/components/schemas/User'
73 '404':
74 $ref: '#/components/responses/NotFound'Defining Schemas#
1components:
2 schemas:
3 User:
4 type: object
5 required:
6 - id
7 - email
8 - name
9 properties:
10 id:
11 type: string
12 format: uuid
13 example: '123e4567-e89b-12d3-a456-426614174000'
14 email:
15 type: string
16 format: email
17 example: 'john@example.com'
18 name:
19 type: string
20 minLength: 1
21 maxLength: 100
22 example: 'John Doe'
23 role:
24 type: string
25 enum: [user, admin, editor]
26 default: user
27 createdAt:
28 type: string
29 format: date-time
30 avatar:
31 type: string
32 format: uri
33 nullable: true
34
35 CreateUserRequest:
36 type: object
37 required:
38 - email
39 - name
40 - password
41 properties:
42 email:
43 type: string
44 format: email
45 name:
46 type: string
47 minLength: 1
48 password:
49 type: string
50 minLength: 8
51 format: password
52
53 UserList:
54 type: object
55 properties:
56 data:
57 type: array
58 items:
59 $ref: '#/components/schemas/User'
60 pagination:
61 $ref: '#/components/schemas/Pagination'
62
63 Pagination:
64 type: object
65 properties:
66 page:
67 type: integer
68 limit:
69 type: integer
70 total:
71 type: integer
72 totalPages:
73 type: integer
74
75 Error:
76 type: object
77 properties:
78 code:
79 type: string
80 message:
81 type: string
82 details:
83 type: objectReusable Components#
1components:
2 parameters:
3 UserId:
4 name: id
5 in: path
6 required: true
7 description: User ID
8 schema:
9 type: string
10 format: uuid
11
12 responses:
13 NotFound:
14 description: Resource not found
15 content:
16 application/json:
17 schema:
18 $ref: '#/components/schemas/Error'
19 example:
20 code: NOT_FOUND
21 message: Resource not found
22
23 Unauthorized:
24 description: Authentication required
25 content:
26 application/json:
27 schema:
28 $ref: '#/components/schemas/Error'
29
30 BadRequest:
31 description: Invalid request
32 content:
33 application/json:
34 schema:
35 $ref: '#/components/schemas/Error'
36
37 securitySchemes:
38 BearerAuth:
39 type: http
40 scheme: bearer
41 bearerFormat: JWT
42
43 ApiKeyAuth:
44 type: apiKey
45 in: header
46 name: X-API-Key
47
48security:
49 - BearerAuth: []Express with Swagger UI#
1import express from 'express';
2import swaggerUi from 'swagger-ui-express';
3import YAML from 'yamljs';
4
5const app = express();
6
7// Load OpenAPI spec
8const openApiSpec = YAML.load('./openapi.yaml');
9
10// Serve Swagger UI
11app.use('/docs', swaggerUi.serve, swaggerUi.setup(openApiSpec, {
12 customCss: '.swagger-ui .topbar { display: none }',
13 customSiteTitle: 'My API Documentation',
14}));
15
16// Serve raw spec
17app.get('/openapi.json', (req, res) => {
18 res.json(openApiSpec);
19});Auto-Generate from Code#
1// Using tsoa for TypeScript
2import { Controller, Get, Post, Route, Body, Path, Query, Security } from 'tsoa';
3
4@Route('users')
5export class UsersController extends Controller {
6 /**
7 * Get a list of users
8 * @param page Page number
9 * @param limit Items per page
10 */
11 @Get()
12 public async getUsers(
13 @Query() page: number = 1,
14 @Query() limit: number = 20
15 ): Promise<UserListResponse> {
16 // Implementation
17 }
18
19 /**
20 * Create a new user
21 * @param body User creation data
22 */
23 @Post()
24 @Security('bearerAuth')
25 public async createUser(
26 @Body() body: CreateUserRequest
27 ): Promise<User> {
28 // Implementation
29 }
30
31 /**
32 * Get user by ID
33 * @param id User's unique identifier
34 */
35 @Get('{id}')
36 public async getUser(@Path() id: string): Promise<User> {
37 // Implementation
38 }
39}Code Generation#
1# Generate TypeScript client
2npx openapi-generator-cli generate \
3 -i openapi.yaml \
4 -g typescript-axios \
5 -o ./generated/client
6
7# Generate server stubs
8npx openapi-generator-cli generate \
9 -i openapi.yaml \
10 -g typescript-express-server \
11 -o ./generated/server1// Using generated client
2import { UsersApi, Configuration } from './generated/client';
3
4const api = new UsersApi(new Configuration({
5 basePath: 'https://api.example.com/v1',
6 accessToken: 'your-token',
7}));
8
9const users = await api.listUsers({ page: 1, limit: 20 });
10const user = await api.createUser({
11 createUserRequest: {
12 email: 'john@example.com',
13 name: 'John',
14 password: 'secure123',
15 },
16});Validation with OpenAPI#
1import { OpenAPIValidator } from 'express-openapi-validator';
2
3app.use(
4 OpenAPIValidator.middleware({
5 apiSpec: './openapi.yaml',
6 validateRequests: true,
7 validateResponses: true,
8 })
9);
10
11// Requests are automatically validated
12// Invalid requests return 400 with detailsBest Practices#
1# Good documentation practices
2
3# 1. Use descriptive summaries
4paths:
5 /users/{id}/posts:
6 get:
7 summary: Get all posts by a specific user
8 description: |
9 Returns a paginated list of posts authored by the user.
10 Posts are sorted by creation date in descending order.
11
12# 2. Provide examples
13components:
14 schemas:
15 User:
16 properties:
17 email:
18 type: string
19 example: 'jane@example.com'
20
21# 3. Document error responses
22responses:
23 '400':
24 description: |
25 Invalid request. Possible reasons:
26 - Missing required fields
27 - Invalid email format
28 - Password too short
29
30# 4. Use tags for organization
31tags:
32 - name: Users
33 description: |
34 User management endpoints.
35 All endpoints require authentication except registration.Conclusion#
OpenAPI documentation serves as a contract between API providers and consumers. Invest in good documentation—it reduces support burden and improves developer adoption.
Generate docs from code when possible, validate requests against the spec, and keep documentation in sync with implementation.