Guide
Choosing Between Full, Request‑Only, or No JSON Schema in Fastify Routes
Learn when to apply Full, Request‑Only, or no JSON Schema in Fastify routes, see a compact comparison table, and follow a concrete route‑level implementation with verification steps.
Published by Tasadduq Burney
15 Jul 2025, 19:05 UTC
4 min94.6K views0

Decision and Constraints
When adding a route to a Fastify server you can choose how much validation and serialization to delegate to the framework’s built‑in JSON Schema support. The decision hinges on traffic volume, data sensitivity, and development speed.
Option Comparison
| Option | What is validated | What is serialized | Typical use case |
|---|---|---|---|
| Full request + response schema | body, querystring, params, headers (if defined) and response payload | Response is run through fast‑json‑stringify using the response schema | Public, high‑traffic endpoints where safety and performance matter |
| Request‑only schema | Same request validation as above, no response schema | Response is serialized with the default JSON.stringify | Internal or low‑traffic routes where you still want guard‑rails on input |
| No schema | None (validation skipped) | Default JSON.stringify | Prototyping or admin endpoints where speed of iteration outweighs safety |
Trade‑offs
- Safety: Full schemas reject malformed input early and strip unexpected output fields, reducing the chance of accidental data leaks.
- Performance: Ajv compiles the schema once at startup; validation cost per request is minimal. fast‑json‑stringify is usually faster than JSON.stringify for large or frequently returned objects.
- Development ergonomics: Skipping schemas removes the need to maintain JSON Schema files, but you lose compile‑time safety and runtime guarantees.
- Startup overhead: Large numbers of schemas increase boot time and memory consumption; measure if you have many shared definitions.
Concrete Implementation
Below is a route‑level example that uses a shared error schema and defines both body and response schemas. The code assumes you have already created a Fastify instance named fastify.
// 1. Shared error schema (added once at bootstrap)
fastify.addSchema({
$id: 'errorResponse',
type: 'object',
properties: {
statusCode: { type: 'integer' },
error: { type: 'string' },
message: { type: 'string' }
},
required: ['statusCode', 'error', 'message'],
additionalProperties: false
});
// 2. Define the route
fastify.post('/users', {
schema: {
body: {
type: 'object',
properties: {
username: { type: 'string', minLength: 3 },
age: { type: 'integer', minimum: 0 }
},
required: ['username', 'age'],
additionalProperties: false
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'string' },
username: { type: 'string' },
createdAt: { type: 'string', format: 'date-time' }
},
required: ['id', 'username', 'createdAt'],
additionalProperties: false
},
// reuse the shared error schema for 4xx/5xx responses
'4xx': { $ref: 'errorResponse#' },
'5xx': { $ref: 'errorResponse#' }
}
}
}, async (request, reply) => {
// Handler logic – Fastify will have already validated request.body
const user = {
id: Math.random().toString(36).substr(2, 9),
username: request.body.username,
createdAt: new Date().toISOString()
};
// reply.send will automatically serialize according to the response schema
return user;
});
Verification Steps
- Start the server and send a request that violates the body schema (e.g., missing
username). You should receive a 400 response whose shape matches the shared error schema. - Send a valid request and inspect the response body; only the fields declared in the 200 response schema (
id,username,createdAt) should be present, even if the handler would have added extra properties. - To confirm the serialization benefit, run a benchmark tool (e.g., autocannon) against the route with the response schema enabled, then repeat after removing the
responsekey from the schema. Compare the average latency; the version with fast‑json‑stringify is typically equal or lower. - Check that the shared schema is reachable: define another route that references
$ref: 'errorResponse#'in its response and verify that a 400 from that route also returns the same error shape.
Limitations and Practical Checks
- Ajv’s strict mode and default coercion can differ between Fastify major versions; consult the release notes for the bundled Ajv version before upgrading and run your test suite against the new version.
- Because the response schema acts as an allowlist, any field you add to the handler output but forget to declare in the schema will be silently stripped. Add a unit test that asserts the exact payload shape to catch mismatches early.
- fast‑json‑stringify handles most JSON Schema constructs, but complex combinations like nested
anyOfwith overlapping types may produce different output thanJSON.stringify. Validate the serialized output for those cases by comparing to a known‑good reference. - Schema compilation adds to startup time; if you notice a significant increase, consider lazy‑loading rarely used schemas or consolidating duplicate definitions with
addSchema.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.