Fastify Best Practices #5: Generating Swagger Documentation
When developing server-side APIs, maintaining accurate, real-time, and easy-to-understand API documentation is key to improving team collaboration and the external developer experience. Writing documentation by hand is not only time-consuming but also prone to inconsistencies between the code and the docs. Fortunately, in the Fastify ecosystem, we can use two powerful tools, @fastify/swagger and @fastify/swagger-ui, to directly transform our code (especially Zod schemas) into professional, interactive Swagger documentation, achieving “code as documentation.”
First, we need to install the relevant dependencies:
npm install @fastify/swagger @fastify/swagger-ui
Configuring the Swagger Plugins
Next, we need to register these two plugins with our Fastify instance. It’s good practice to handle this configuration in a separate plugin file (e.g., plugins/swagger.ts).
The core idea here is to enable @fastify/swagger to understand the Zod schemas we highly recommended in the previous article.
import swagger from '@fastify/swagger';
import swaggerUi from '@fastify/swagger-ui';
import fastify, { FastifyInstance } from 'fastify';
import { jsonSchemaTransform } from 'fastify-type-provider-zod';
export const setupSwagger = async (server: FastifyInstance) => {
// Register the core swagger plugin
await server.register(swagger, {
// Key: make swagger support Zod schemas
transform: jsonSchemaTransform,
openapi: {
openapi: '3.0.0', // OpenAPI Version
info: {
// Basic document information
title: 'Fastify Best Practice Project API',
description: 'This is the API documentation for a sample project',
version: '0.1.0',
},
servers: [
// API server list
{
url: `http://127.0.0.1:8080`,
description: 'Development server',
},
],
tags: [
// API category tags
{ name: 'user', description: 'User-related APIs' },
{ name: 'health', description: 'Health check APIs' },
],
// Optional: Add global security definitions
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
security: [
{
bearerAuth: [],
},
],
},
});
// Register the swagger UI plugin to provide the visual interface
await server.register(swaggerUi, {
routePrefix: '/doc', // Access path for the swagger documentation
uiConfig: {
docExpansion: 'list', // 'full', 'list', or 'none'
deepLinking: true,
},
});
};
The Role of jsonSchemaTransform
As you may have noticed, the key here is the transform: jsonSchemaTransform configuration. This function, imported from fastify-type-provider-zod, acts as a bridge. It automatically converts the Zod schemas you define in your routes into the JSON Schema format required by Swagger (the OpenAPI specification). Without it, Swagger would not be able to understand our Zod type definitions.
Adding Documentation Info to Routes
After configuring the plugins, we need to provide some metadata in the schema option of each API route. Swagger reads this information to generate the documentation.
Let’s look at a concrete example:
import { FastifyInstance } from 'fastify';
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
import { z } from 'zod';
const routes = async (fastify: FastifyInstance) => {
fastify.withTypeProvider<ZodTypeProvider>().get(
'/user/:id', // API URL
{
schema: {
summary: 'Get single user information',
description: 'Queries for user details based on the user ID.',
tags: ['user'], // API Category
params: z.object({
id: z.string().uuid().describe("User's unique ID (UUID format)"),
}),
response: {
200: z
.object({
id: z.string().uuid(),
name: z.string(),
email: z.string().email(),
})
.describe('Successful response'),
404: z
.object({
message: z.string(),
})
.describe('Response when user is not found'),
},
// Indicates this endpoint requires bearerAuth
security: [{ bearerAuth: [] }],
},
},
async (req, reply) => {
// ... business logic ...
const user = { id: req.params.id, name: 'Millet', email: 'millet@example.com' };
return reply.status(200).send(user);
},
);
};
export default routes;
Tip: Zod’s .describe() method is a very useful trick. jsonSchemaTransform intelligently captures its content and displays it as the field description in the Swagger UI, making your API documentation much clearer.
Summary
With the configuration above, we not only generate professional, interactive API documentation for our project, but more importantly, this documentation is “living documentation” that is perfectly in sync with our code. Whenever your Zod schema changes, the documentation updates automatically. This completely eliminates the problem of inconsistencies between code and documentation that arises from manual writing. It greatly improves development efficiency and the maintainability of the API, making it an indispensable part of modern server-side development.
In my open-source project, I’ve encapsulated the Swagger configuration in a separate plugin (04.swagger.plugin.ts) and loaded it automatically via fastify-autoload. You are welcome to use it as a reference: