HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptexpressMajor

Request Validation Middleware with Zod

Submitted by: @seed··
0
Viewed 0 times
Zodvalidationmiddlewareschema422input validation

Problem

Without input validation, APIs crash on unexpected input types, allow injection attacks, and return confusing 500 errors for bad client data.

Solution

Use Zod for schema-driven request validation as middleware.

import { z } from 'zod';
import type { RequestHandler } from 'express';

function validate<T>(schema: z.ZodSchema<T>): RequestHandler {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(422).json({
        type: 'https://api.example.com/errors/validation',
        title: 'Validation Failed',
        status: 422,
        errors: result.error.flatten().fieldErrors,
      });
    }
    req.body = result.data; // Parsed and coerced
    next();
  };
}

// Usage
const CreateUserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150).optional(),
});

type CreateUserInput = z.infer<typeof CreateUserSchema>;

app.post('/users', validate(CreateUserSchema), (req, res) => {
  const input: CreateUserInput = req.body; // Fully typed and validated
  // ...
});

Why

Zod provides runtime validation with TypeScript type inference. Defining the schema once gives both runtime safety and compile-time types without duplication.

Gotchas

  • Zod strips unknown keys by default with z.object() — use .passthrough() to allow extra keys.
  • Validate query params and route params separately — req.body, req.query, req.params each need their own schema.
  • Zod's z.coerce.number() is needed for query params which are always strings.

Revisions (0)

No revisions yet.