Fastify JSON Schema Validation: Decision Guide and Implementation
Learn when to use Fastify's built‑in JSON Schema for request validation and response serialization, see trade‑offs, and view a shared‑schema example.
06 Sept 2025, 03:58 UTC

Decision: Use JSON Schema for Validation and Serialization
When building a Fastify API you must decide how to validate incoming data and shape outgoing responses. You can write manual checks inside each handler, use a runtime library like Zod, or declare a JSON Schema in the route options. Manual validation gives you full control but repeats work on every request, while a runtime library adds bundle size and still runs per request. Fastify’s built‑in JSON Schema support shifts the cost to startup by compiling schemas with Ajv and fast‑json‑stringify.
Supported Options
| Option | Runtime Cost | Startup Impact | Typical Use |
|---|---|---|---|
| No schema (manual checks) | High – repeated if/else or library calls | None | Simple prototypes or dynamic payloads |
| Request schema only | Low – compiled validation function | Medium – Ajv compilation | Most REST endpoints |
| Request + response schema | Low – validation + fast‑json‑stringify | Medium‑High – two compilations | APIs where payload shape matters |
Trade‑offs
Startup Time vs. Request‑Per‑Second
Fastify compiles each schema into a JavaScript function during fastify.ready(). A large number of complex schemas can add seconds to startup, which matters in serverless cold starts. In long‑running containers the cost is paid once and amortized over millions of requests, usually yielding higher RPS.
Response Schema as a Filter
The response schema does not merely describe the output; it actively removes any property that is not listed. This prevents accidental leakage of sensitive fields but also means you must keep the schema in sync with the handler’s return value.
Implementation: Shared Schemas and Validation
To avoid duplication, define reusable schemas with fastify.addSchema() and reference them via $ref. The example below shows a shared user schema used for request validation and a response schema that strips the password field.
const fastify = require('fastify')({ logger: true });
// Shared schema
fastify.addSchema({
id: 'user',
type: 'object',
properties: {
username: { type: 'string', minLength: 3 },
email: { type: 'string', format: 'email' }
},
required: ['username', 'email']
});
// Route using the shared schema
fastify.post('/register', {
schema: {
body: { $ref: '#$defs/user' },
response: {
201: {
type: 'object',
properties: {
id: { type: 'integer' },
username: { type: 'string' }
},
required: ['id', 'username']
}
}
},
async (request, reply) => {
// password is omitted intentionally; it will be stripped by the response schema
return {
id: 123,
username: request.body.username,
password: request.body.password // will not appear in the final JSON
};
}
});
fastify.listen({ port: 3000, host: '0.0.0.0' }, (err, address) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
fastify.log.info(`Server listening at ${address}`);
});
Verification and Diagnostics
1. Confirm Request Validation
Send a payload that violates the schema (for example, a username shorter than three characters or an invalid email). Fastify will return a 400 Bad Request before the handler runs.
2. Confirm Response Filtering
Send a valid request and inspect the response body. Any field not declared in the response schema, such as the password, will be absent from the returned JSON.
3. Measure Startup Impact (optional)
Record the time between require('fastify') and the first listening event with and without the schema to see the compilation cost.
4. Measure Runtime Throughput
Use a tool such as autocannon to compare requests‑per‑second for a route with a schema versus a route that performs manual validation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.