This error occurs in Fastify when you attempt to validate request data without providing a schema definition. Fastify requires an explicit schema for request validation, and if missing, it will throw this error. The schema defines the expected structure of request parameters, query strings, bodies, or headers.
Fastify is a highly performant web framework that requires explicit schema definitions for request validation. Unlike other frameworks that may perform implicit validation, Fastify demands that you provide a schema when you want to validate incoming data. This error typically occurs when you're using Fastify's validation features but have forgotten to define the schema object. The schema can be a JSON Schema that describes the expected format of request data (body, query parameters, route parameters, or headers). Without a schema, Fastify cannot validate the incoming requests and throws this error. This is intentional design in Fastify - explicit schema definitions provide performance benefits through pre-compilation and type safety. It prevents accidental validation issues and makes your API contract clear.
Fastify requires explicit schema definitions using JSON Schema format. A basic route with validation looks like:
const schema = {
body: {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' }
},
required: ['name']
}
};
fastify.post('/users', { schema }, async (request, reply) => {
// request.body is validated against schema.body
return { id: 1, ...request.body };
});The schema object can include:
- body - Schema for request body
- querystring - Schema for query parameters
- params - Schema for URL path parameters
- headers - Schema for HTTP headers
- response - Schema for response validation
If you're getting the "schema is required" error, ensure your route options include a schema object:
Before (incorrect):
fastify.post('/api/data', async (request, reply) => {
// No schema provided - will fail if validation is enabled
return request.body;
});After (correct):
const createDataSchema = {
body: {
type: 'object',
properties: {
title: { type: 'string' },
description: { type: 'string' }
},
required: ['title']
}
};
fastify.post('/api/data', { schema: createDataSchema }, async (request, reply) => {
// request.body is automatically validated
return { id: 1, ...request.body };
});The second parameter to route handlers is the options object where schema is defined.
Create proper JSON Schema definitions for your request bodies:
const userSchema = {
body: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email'
},
password: {
type: 'string',
minLength: 8
},
age: {
type: 'integer',
minimum: 18
}
},
required: ['email', 'password'],
additionalProperties: false
}
};
fastify.post('/register', { schema: userSchema }, async (request, reply) => {
// Automatically validates against schema
const { email, password, age } = request.body;
// ... process registration
});Key JSON Schema properties:
- type - Data type (string, number, object, array, etc.)
- properties - Object property definitions
- required - Array of required properties
- additionalProperties - Allow/disallow extra properties
- minLength, maxLength - String length constraints
- minimum, maximum - Number range constraints
Define schemas for query string parameters:
const searchSchema = {
querystring: {
type: 'object',
properties: {
q: { type: 'string' },
limit: { type: 'integer', default: 10 },
offset: { type: 'integer', default: 0 }
},
required: ['q']
}
};
fastify.get('/search', { schema: searchSchema }, async (request, reply) => {
const { q, limit, offset } = request.query;
// Query parameters are validated and typed
return { results: [] };
});Query parameters are validated against the querystring schema before reaching your handler.
Define schemas for URL path parameters:
const getUserSchema = {
params: {
type: 'object',
properties: {
id: { type: 'string' }
},
required: ['id']
}
};
fastify.get('/users/:id', { schema: getUserSchema }, async (request, reply) => {
const { id } = request.params;
// id is validated before the handler runs
return { id, name: 'John' };
});The params schema validates route parameters like :id in the URL.
For complex applications, define reusable schemas and reference them:
const userEmailSchema = {
$id: 'http://example.com/schemas/user-email.json',
type: 'object',
properties: {
email: { type: 'string', format: 'email' }
},
required: ['email']
};
fastify.addSchema(userEmailSchema);
const createUserSchema = {
body: {
allOf: [
{ $ref: 'http://example.com/schemas/user-email.json' },
{
type: 'object',
properties: {
password: { type: 'string', minLength: 8 },
name: { type: 'string' }
},
required: ['password', 'name']
}
]
}
};
fastify.post('/users', { schema: createUserSchema }, async (request, reply) => {
// Validates using combined schema
return { id: 1, ...request.body };
});This allows schema reuse across multiple routes.
For TypeScript projects, use Zod for type-safe schemas:
import Fastify from 'fastify';
import {
fastifyZodOpenApi,
z
} from '@fastify/zod-openapi';
const fastify = Fastify();
fastify.register(fastifyZodOpenApi);
const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string()
});
const route = {
method: 'POST',
url: '/users',
schema: {
body: createUserSchema
},
handler: async (request, reply) => {
// request.body is typed
return { id: 1, ...request.body };
}
};
fastify.route(route);Zod provides better type inference and validation than raw JSON Schema.
Schema Performance Benefits
Fastify compiles schemas to optimized validation code at startup. This is why schemas must be defined upfront - it allows Fastify to pre-compile validators.
JSON Schema Standards
Fastify uses JSON Schema Draft 7 by default. You can change the dialect:
const fastify = Fastify({
jsonShorthand: false, // Use full schema format
ajv: {
keywords: ['example'] // Allow custom keywords
}
});Conditional Validation
Use JSON Schema composition for conditional validation:
const schema = {
body: {
type: 'object',
properties: {
type: { enum: ['email', 'phone'] },
value: { type: 'string' }
},
required: ['type', 'value'],
if: { properties: { type: { const: 'email' } } },
then: { properties: { value: { format: 'email' } } },
else: { properties: { value: { pattern: '^\d+$' } } }
}
};Custom Validation Keywords
Add custom validation logic:
fastify.setValidationCompiler(({ schema }) => {
return data => {
// Custom validation logic
if (data.name && data.name.length < 2) {
return { valid: false, error: 'Name too short' };
}
return { valid: true };
};
});Error Handling
Validation errors are automatically caught by Fastify and return 400 status:
fastify.setErrorHandler((error, request, reply) => {
if (error.validation) {
reply.status(400).send({
error: 'Validation error',
details: error.validation
});
} else {
reply.send(error);
}
});Testing with Schemas
Schemas help with testing and documentation. Use them with OpenAPI integration:
const fastify = Fastify();
fastify.register(require('@fastify/swagger'));
fastify.register(require('@fastify/swagger-ui'));
// Routes with schemas auto-generate OpenAPI docsError: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files
Error: cluster.fork() failed (cannot create child process)
cluster.fork() failed - Cannot create child process