Fastify Validation: Choosing Between JSON Schema and Custom Logic
Learn when to use Fastify's built-in JSON Schema validation versus custom preHandler logic to optimize API performance and maintain data integrity.
01 Sept 2026, 20:59 UTC

The Validation Decision
When building APIs with Fastify, you must decide where to enforce data integrity: within a declarative JSON Schema or via imperative custom logic. The primary challenge is balancing the high performance of Fastify's built-in serialization with the need for complex, dynamic business rules that a static schema cannot express.
The key takeaway is that JSON Schema should be your first line of defense. Because Fastify compiles these schemas into optimized JavaScript functions using Ajv (Another JSON Validator), it handles type checking and response serialization significantly faster than manual validation. Custom logic should be reserved exclusively for constraints that require database lookups or cross-field dependencies.
Comparison: Schema Validation vs. Custom Logic
| Feature | JSON Schema (Ajv) | Custom Logic (preHandler) |
|---|---|---|
| Execution Timing | Pre-handler (Automatic) | Explicit hook execution |
| Performance | High (Compiled functions) | Variable (Runtime overhead) |
| Response Optimization | Yes (via fast-json-stringify) |
No |
| Complexity | Static types/formats | Dynamic/Database-driven |
| Error Handling | Automatic 400 Bad Request | Manual error throwing |
Trade-offs and Constraints
Schema Compilation Overhead: Fastify compiles schemas during server startup. While this makes requests faster, extremely large or deeply nested schemas can increase the initial boot time of your application.
Serialization Gains: One of Fastify's most powerful features is using the response schema to optimize the JSON stringification process. If you bypass the schema and use custom logic for everything, you lose the performance boost provided by fast-json-stringify, as Fastify must fall back to the standard JSON.stringify().
The "Double-Pass" Requirement: For many production routes, a hybrid approach is necessary. You use the schema to ensure the payload is structurally sound (e.g., "email is a string and formatted correctly") and a preHandler hook to ensure the data is logically valid (e.g., "email is not already registered in the database").
Implementation Example: Hybrid Validation
This example demonstrates a user registration route. It uses a schema for structural integrity and a preHandler for a database uniqueness check. This assumes Fastify v4.x.
const fastify = require('fastify')({ logger: true });
// 1. Define a shared schema for request and response
const userSchema = {
body: {
type: 'object',
required: ['email', 'password'],
properties: {
email: { type: 'string', format: 'email' },
password: { type: 'string', minLength: 8 }
}
},
response: {
201: {
type: 'object',
properties: {
userId: { type: 'string' },
status: { type: 'string' }
}
}
}
};
// 2. Custom validation logic (e.g., Database check)
const checkUserExists = async (request, reply) => {
const { email } = request.body;
// Mock database call
const userExists = await Promise.resolve(email === 'exists@example.com');
if (userExists) {
reply.code(409).send({ error: 'User already exists' });
}
};
// 3. Route definition
fastify.post('/register', {
schema: userSchema,
preHandler: [checkUserExists]
}, async (request, reply) => {
return { userId: '12345', status: 'created' };
});
fastify.listen({ port: 3000 });
Execution and Verification
To verify this implementation, run the following tests using a tool like curl or Postman:
- Test Structural Validation: Send a request with a missing password. Expected Result:
400 Bad Requestreturned automatically by Fastify before thepreHandleris ever called. - Test Custom Logic: Send a request with
"email": "exists@example.com". Expected Result:409 Conflictreturned by thepreHandler. - Test Success: Send a valid, unique email. Expected Result:
201 Createdwith the optimized JSON response.
Limitations
JSON Schema cannot perform asynchronous operations. If your validation requires an API call or a database query, you cannot use the schema property for that specific check. Additionally, while Ajv is highly performant, very complex oneOf or allOf keywords can increase the complexity of the compiled function, potentially impacting CPU usage under extreme loads.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.