Validate a Full API Response with Zod Schema
Learn how to convert a complete REST API response including metadata, pagination, and nested data into a comprehensive Zod validation schema.
Advanced
Detailed Explanation
Validating Real API Responses with Zod
Production API responses include more than just data. They contain status codes, pagination info, error envelopes, and deeply nested objects. Zod schemas validate the entire response structure at runtime.
Example JSON
{
"status": "success",
"data": {
"users": [
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"profile": {
"avatar": "https://cdn.example.com/alice.jpg",
"bio": "Full-stack developer"
}
}
],
"pagination": {
"page": 1,
"perPage": 20,
"total": 150,
"totalPages": 8
}
},
"requestId": "req_abc123"
}
Generated Zod Schema
import { z } from "zod";
const profileSchema = z.object({
avatar: z.string().url(),
bio: z.string(),
});
const userSchema = z.object({
id: z.number().int(),
name: z.string(),
email: z.string().email(),
profile: profileSchema,
});
const paginationSchema = z.object({
page: z.number().int().positive(),
perPage: z.number().int().positive(),
total: z.number().int().nonnegative(),
totalPages: z.number().int().nonnegative(),
});
const apiResponseSchema = z.object({
status: z.enum(["success", "error"]),
data: z.object({
users: z.array(userSchema),
pagination: paginationSchema,
}),
requestId: z.string(),
});
type ApiResponse = z.infer<typeof apiResponseSchema>;
Making It Generic
function createPaginatedSchema<T extends z.ZodTypeAny>(itemSchema: T) {
return z.object({
status: z.enum(["success", "error"]),
data: z.object({
items: z.array(itemSchema),
pagination: paginationSchema,
}),
requestId: z.string(),
});
}
const userListSchema = createPaginatedSchema(userSchema);
const productListSchema = createPaginatedSchema(productSchema);
Error Response Handling
const errorSchema = z.object({
status: z.literal("error"),
error: z.object({
code: z.string(),
message: z.string(),
}),
requestId: z.string(),
});
const responseSchema = z.union([apiResponseSchema, errorSchema]);
Integrating with fetch
async function fetchUsers(): Promise<ApiResponse> {
const res = await fetch("/api/users");
const json = await res.json();
return apiResponseSchema.parse(json);
// Throws ZodError if response shape is invalid
}
This pattern catches API contract violations immediately, before malformed data propagates through your application.
Use Case
You are building a frontend client library for your REST API and want runtime validation of every response to catch backend contract violations before they cause UI errors.