Skip to content

Instantly share code, notes, and snippets.

@lwensveen
Last active April 17, 2025 16:28
Show Gist options
  • Save lwensveen/657b91af48aa43b860421caf7978e1c1 to your computer and use it in GitHub Desktop.
Save lwensveen/657b91af48aa43b860421caf7978e1c1 to your computer and use it in GitHub Desktop.
Reproduction for [email protected] validation error: data/required must be array

Reproduction for fastify-type-provider-zod Validation Error

This repository reproduces a validation error with [email protected] when using Zod schemas with Fastify routes. The error occurs for a GET route with a querystring schema and may affect POST routes with a body schema.

Environment Node.js: v18.x Fastify: 5.2.1 fastify-type-provider-zod: 4.0.2 Zod: 3.24.2 TypeScript: 5.7.3

Error

Failed building the validation schema for GET: /api/example, due to error schema is invalid: data/required must be array

Steps to Reproduce

  1. Install dependencies:
    npm install
    

npm start

curl "http://localhost:3000/api/example?limit=10&offset=0"

Failed building the validation schema for GET: /api/example, due to error schema is invalid: data/required must be array

curl http://localhost:3000/debug-schema

import Fastify, { FastifyInstance } from 'fastify';
import routes from './routes';
import { ZodTypeProvider, hasZodFastifySchemaValidationErrors } from 'fastify-type-provider-zod';
import { exampleQuerySchema, exampleInsertSchema } from './types';
// Create a Fastify instance
const app: FastifyInstance = Fastify({
logger: {
level: 'debug',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'yyyy-mm-dd HH:MM:ss',
ignore: 'pid,hostname',
},
},
},
}).withTypeProvider<ZodTypeProvider>();
// Register routes
app.register(routes, { prefix: '/api' });
// Custom error handler for Zod validation errors
app.setErrorHandler((err, req, reply) => {
if (hasZodFastifySchemaValidationErrors(err)) {
console.error('Validation Error:', JSON.stringify(err.validation, null, 2));
return reply.code(400).send({
error: 'Validation Error',
message: "Request doesn't match the schema",
statusCode: 400,
details: { issues: err.validation, method: req.method, url: req.url },
});
}
reply.send(err);
});
// Debug route for schemas
app.get('/debug-schema', async (request, reply) => {
const querySchema = exampleQuerySchema._def;
const insertSchema = exampleInsertSchema._def;
console.log('exampleQuerySchema JSON Schema:', JSON.stringify(querySchema, null, 2));
console.log('exampleInsertSchema JSON Schema:', JSON.stringify(insertSchema, null, 2));
reply.send({ querySchema, insertSchema });
});
// Start the server
const start = async () => {
try {
await app.listen({ host: 'localhost', port: 3000 });
app.log.info('Server is listening on localhost:3000');
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();
{
"name": "repro-fastify-zod",
"version": "1.0.0",
"description": "Reproduction for fastify-type-provider-zod validation error",
"main": "src/main.ts",
"scripts": {
"start": "ts-node src/main.ts",
"build": "tsc"
},
"dependencies": {
"fastify": "^5.2.1",
"fastify-type-provider-zod": "^4.0.2",
"zod": "^3.24.2"
},
"devDependencies": {
"ts-node": "^10.9.1",
"typescript": "^5.7.3"
}
}
import { FastifyInstance } from 'fastify';
import { z } from 'zod';
import {
exampleQuerySchema,
exampleInsertSchema,
Example,
CreateExampleInput,
} from './types';
// Mock response schema
const exampleResponseSchema = z.object({
id: z.string().uuid(),
name: z.string(),
age: z.number().int(),
email: z.string().email().optional(),
});
const errorResponseSchema = z.object({
code: z.string(),
message: z.string(),
statusCode: z.number(),
});
async function routes(fastify: FastifyInstance) {
// GET /example - Get examples with query parameters
fastify.get<{
Querystring: z.infer<typeof exampleQuerySchema>;
}>(
'/example',
{
schema: {
querystring: exampleQuerySchema,
response: {
200: z.array(exampleResponseSchema),
},
},
},
async (request, reply) => {
console.log('GET /example Query Schema:', JSON.stringify(exampleQuerySchema._def, null, 2));
const { limit, offset } = request.query;
// Mock response
const examples: Example[] = [
{ id: '123e4567-e89b-12d3-a456-426614174000', name: 'John Doe', age: 30 },
];
return examples;
}
);
// POST /example - Create a new example
fastify.post<{
Body: CreateExampleInput;
}>(
'/example',
{
schema: {
body: exampleInsertSchema.omit({ userId: true }),
response: {
201: exampleResponseSchema,
},
},
},
async (request, reply) => {
console.log('POST /example Body Schema:', JSON.stringify(exampleInsertSchema._def, null, 2));
const userId = 'mock-user-id';
const input = exampleInsertSchema.parse({ ...request.body, userId });
// Mock response
const result: Example = {
id: '123e4567-e89b-12d3-a456-426614174000',
name: input.name,
age: input.age,
email: input.email,
};
return reply.code(201).send(result);
}
);
}
export default routes;
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
import { z } from 'zod';
// Query schema for GET /example
export const exampleQuerySchema = z.object({
limit: z.number().int().min(1).optional().default(10),
offset: z.number().int().min(0).optional().default(0),
});
// Body schema for POST /example
export const exampleInsertSchema = z.object({
name: z.string(),
age: z.number().int().min(0),
email: z.string().email().optional(),
userId: z.string().uuid().optional(),
});
// Type for response
export interface Example {
id: string;
name: string;
age: number;
email?: string;
}
// Type for create input (excludes userId)
export type CreateExampleInput = z.infer<typeof exampleInsertSchema.omit({ userId: true })>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment