Examples
How to use together with @fastify/swagger
This code sets up a Fastify web server with Swagger documentation and Zod-based type validation. This Fastify server:
- Uses Zod for input validation.
- Automatically generates OpenAPI documentation from Zod schemas.
- Exposes a /login POST endpoint with request body validation.
- Provides Swagger UI at /documentation.
import fastifySwagger from '@fastify/swagger';
import fastifySwaggerUI from '@fastify/swagger-ui';
import type { ZodTypeProvider } from '@marcalexiei/fastify-type-provider-zod';
import {
jsonSchemaTransform,
serializerCompiler,
validatorCompiler,
} from '@marcalexiei/fastify-type-provider-zod';
import fastify from 'fastify';
import { z } from 'zod';
const app = fastify();
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
app.register(fastifySwagger, {
openapi: {
info: {
title: 'SampleApi',
description: 'Sample backend service',
version: '1.0.0',
},
servers: [],
},
transform: jsonSchemaTransform,
// You can also create transform with custom skiplist of endpoints that should not be included in the specification:
//
// transform: createJsonSchemaTransform({
// skipList: [ '/documentation/static/*' ]
// })
});
app.register(fastifySwaggerUI, {
routePrefix: '/documentation',
});
const LOGIN_SCHEMA = z.object({
username: z.string().max(32).describe('Some description for username'),
password: z.string().max(32),
});
app.after(() => {
app.withTypeProvider<ZodTypeProvider>().route({
method: 'POST',
url: '/login',
schema: { body: LOGIN_SCHEMA },
handler: (_req, res) => {
res.send('ok');
},
});
});
async function run() {
await app.ready();
await app.listen({
port: 4949,
});
app.log.info('Documentation running at http://localhost:4949/documentation');
}
run().catch(() => {});
Customizing error responses
You can customize how Fastify handles request and response validation errors by extending your global error handler. Here's how to do it:
import {
hasZodFastifySchemaValidationErrors,
isResponseSerializationError,
} from '@marcalexiei/fastify-type-provider-zod';
import fastify from 'fastify';
const app = fastify();
app.setErrorHandler((err, req, reply) => {
if (hasZodFastifySchemaValidationErrors(err)) {
return reply.code(400).send({
error: 'Response Validation Error',
message: "Request doesn't match the schema",
statusCode: 400,
details: {
issues: err.validation,
method: req.method,
url: req.url,
},
});
}
if (isResponseSerializationError(err)) {
return reply.code(500).send({
error: 'Internal Server Error',
message: "Response doesn't match the schema",
statusCode: 500,
details: {
issues: err.cause.issues,
method: err.method,
url: err.url,
},
});
}
// the rest of the error handler
return;
});
How to create refs to the schemas?
This plugin automatically generates JSON Schema references via the jsonSchemaTransformObject
function. To enable this, you register your schemas in the global Zod registry and assign each one a unique id
. Once registered, fastifySwagger
will generate an OpenAPI document that references these schemas appropriately.
For example, the following code creates a reference to the User
schema, ensuring it is included and properly linked in the OpenAPI specification.
import fastifySwagger from '@fastify/swagger';
import fastifySwaggerUI from '@fastify/swagger-ui';
import type { ZodTypeProvider } from '@marcalexiei/fastify-type-provider-zod';
import {
jsonSchemaTransform,
jsonSchemaTransformObject,
serializerCompiler,
validatorCompiler,
} from '@marcalexiei/fastify-type-provider-zod';
import fastify from 'fastify';
import { z } from 'zod';
const USER_SCHEMA = z.object({
id: z.number().int().positive(),
name: z.string().describe('The name of the user'),
});
z.globalRegistry.add(USER_SCHEMA, { id: 'User' });
const app = fastify();
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
app.register(fastifySwagger, {
openapi: {
info: {
title: 'SampleApi',
description: 'Sample backend service',
version: '1.0.0',
},
servers: [],
},
transform: jsonSchemaTransform,
transformObject: jsonSchemaTransformObject,
});
app.register(fastifySwaggerUI, {
routePrefix: '/documentation',
});
app.after(() => {
app.withTypeProvider<ZodTypeProvider>().route({
method: 'GET',
url: '/users',
schema: {
response: {
200: USER_SCHEMA.array(),
},
},
handler: (_req, res) => {
res.send([]);
},
});
});
async function run() {
await app.ready();
await app.listen({
port: 4949,
});
app.log.info('Documentation running at http://localhost:4949/documentation');
}
run().catch(() => {});
How to create a plugin?
The plugin provides types that simplify writing both async and callback-style Fastify plugins, with full type inference based on your Zod schemas. This ensures request and response types are automatically inferred, reducing boilerplate and improving type safety across your routes.
import type { FastifyPluginAsyncZod } from '@marcalexiei/fastify-type-provider-zod';
import { z } from 'zod';
export const plugin: FastifyPluginAsyncZod = async (fastify, _opts) => {
fastify.route({
method: 'GET',
url: '/',
// Define your schema
schema: {
querystring: z.object({
name: z.string().min(4),
}),
response: {
200: z.string(),
},
},
handler: (req, res) => {
res.send(req.query.name);
},
});
await Promise.resolve();
};