Schema Validation with Zod
Ensuring the data coming into your API is correct is crucial. BurgerAPI simplifies this with built-in request validation powered by the popular Zod library.
Define schemas for your route's expected query parameters or request body, and BurgerAPI handles the validation before your handler code even runs.
Defining Schemas
Enable validation by exporting a schema
object from your route.ts
file.
- Import
z
fromzod
. - The
schema
object structure:{[httpMethod]: { query?: ZodSchema, body?: ZodSchema }}
.- Keys are lowercase HTTP methods (
get
,post
, etc.). - Define
query
for query parameter validation. - Define
body
for request body validation (typically forPOST
,PUT
,PATCH
).
- Keys are lowercase HTTP methods (
import { z } from "zod";
import type { BurgerRequest, Middleware } from "burger-api";
// Define Zod schemas for GET query params and POST body
export const schema = {
get: {
query: z
.object({
search: z.string().optional(),
limit: z.coerce.number().int().positive().optional().default(10),
sortBy: z.enum(["price", "name"]).optional().default("name"),
})
.strict(), // Disallow extra query parameters
},
post: {
body: z
.object({
name: z.string().min(1, "Product name is required"),
price: z.number().positive("Price must be a positive number"),
tags: z.array(z.string()).nonempty().optional(), // Optional, but if present, must not be empty
})
.strict(), // Disallow extra fields in the body
},
};
// Optional route-specific middleware
export const middleware: Middleware[] = [
(req) => {
console.log("Product route middleware executed");
return undefined;
},
];
// --- Route Handlers ---
// GET handler with typed, validated query parameters
export async function GET(
req: BurgerRequest<{ query: z.infer<typeof schema.get.query> }>
) {
// Access validated data via req.validated
const { search, limit, sortBy } = req.validated?.query ?? {}; // Use default values if validation somehow skipped (shouldn't happen)
console.log(
`Fetching products: search=${search}, limit=${limit}, sort=${sortBy}`
);
// ... fetch products based on validated params ...
return Response.json({
message: "Products fetched successfully",
params: { search, limit, sortBy },
});
}
// POST handler with typed, validated body
export async function POST(
req: BurgerRequest<{ body: z.infer<typeof schema.post.body> }>
) {
// Access validated data via req.validated
const productData = req.validated?.body;
// Type safety! productData has { name: string; price: number; tags?: string[] | undefined }
console.log("Creating product:", productData?.name, productData?.price);
// ... create product in database ...
return Response.json({ message: "Product created", data: productData });
}
Accessing Validated Data
When validation passes:
- BurgerAPI automatically attaches the validated data to
req.validated
. - Use
req.validated.query
for validated query parameters. - Use
req.validated.body
for the validated request body. - Leverage
z.infer<typeof schema.xxx.yyy>
in your handler's type signature (BurgerRequest<...>
) to get full TypeScript type safety and autocompletion on thereq.validated
object!
Automatic Error Handling
You generally don't need to manually check for validation errors in your route handler.
If the incoming request doesn't match the Zod schema you defined, BurgerAPI automatically:
- Stops processing the request (doesn't call your handler).
- Sends a
400 Bad Request
response. - Includes a JSON body detailing the validation errors reported by Zod.
This provides clear feedback to the API consumer about what went wrong.
Middleware Interaction
It's important to know where validation fits in the request lifecycle:
- Global Middleware runs.
- Validation Middleware runs (if
schema
is exported). - Route-Specific Middleware runs.
- Route Handler runs.
This means your route-specific middleware and handler can rely on the data having already been validated if a schema is defined. See the Middleware Guide for more details.