JSON Schema Validation
Use Fastify's built-in JSON Schema for request validation and compiled response serialization.
Search across all documentation pages
Use Fastify's built-in JSON Schema for request validation and compiled response serialization.
Quick-reference recipe card - copy-paste ready.
import Fastify from "fastify";
const app = Fastify();
const userSchema = {
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string", minLength: 1, maxLength: 100 },
email: { type: "string", format: "email" },
},
} as const;
app.post("/users", {
schema: {
body: userSchema,
response: {
201: {
type: "object",
properties: {
id: { type: "string", format: "uuid" },
name: { type: "string" },
email: { type: "string" },
},
},
},
},
}, async (req, reply) => {
const body = req.body as { name: string; email: string };
return reply.status(201).send({ id: crypto.randomUUID(), ...body });
});When to reach for this: Every public API endpoint. Schema validation is Fastify's primary advantage over Express.
import Fastify from "fastify";
import { Type } from "@sinclair/typebox";
import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox";
const app = Fastify().withTypeProvider<TypeBoxTypeProvider>();
const CreateUser = Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ format: "email" }),
});
const UserResponse = Type.Object({
id: Type.String({ format: "uuid" }),
name: Type.String(),
email: Type.String(),
});
app.post("/users", {
schema: {
body: CreateUser,
response: { 201: UserResponse },
},
}, async (req, reply) => {
const { name, email } = req.body;
return reply.status(201).send({ id: crypto.randomUUID(), name, email });
});
app.setErrorHandler((err, _req, reply) => {
if (err.validation) {
return reply.status(400).send({ error: "Validation failed", details: err.validation });
}
reply.status(500).send({ error: "Internal server error" });
});What this demonstrates:
preValidation hook before the handler| Part | Validates | Failure status |
|---|---|---|
body | POST/PUT/PATCH body | 400 |
querystring | URL query params | 400 |
params | Route params (:id) | 400 |
headers | Request headers | 400 |
response | Handler return value | 500 (serialization) |
app.addSchema({ $id: "User", type: "object", properties: { id: { type: "string" } } });
app.get("/users/:id", {
schema: {
params: { $ref: "User#" },
response: { 200: { $ref: "User#" } },
},
}, async (req) => ({ id: req.params.id }));additionalProperties - accepts unexpected fields. Fix: set additionalProperties: false on objects.$ref and addSchema.fastify-type-provider-zod.| Alternative | Use When | Don't Use When |
|---|---|---|
| Zod type provider | Team already standardized on Zod | Maximum serialization perf (TypeBox/JSON Schema faster) |
| Ajv standalone in Express | Stuck on Express | Starting new Fastify project |
| OpenAPI code generation | Contract-first with external consumers | Internal-only API |
| Manual validation in handler | Quick prototype only | Any production endpoint |
Validation adds microseconds. Compiled serializers speed up responses, often netting a performance gain over JSON.stringify.
Yes. @fastify/swagger reads route schemas and generates OpenAPI 3 documentation automatically.
Omit from required array. Use { type: ["string", "null"] } for nullable fields.
Fastify 5 uses Ajv with JSON Schema draft-07 by default. Check @fastify/ajv-compiler for options.
Not recommended. Use looser schemas for dev-only routes if needed, but keep validation on public endpoints.
Multipart uses @fastify/multipart with separate validation. JSON Schema does not cover file fields.
Yes for public APIs. It guarantees contract compliance and enables fast serialization.
NestJS uses class-validator decorators. Fastify uses JSON Schema at the route level. Both validate before handler execution.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 18, 2026