Now let’s implement the actual middleware functions that use our Zod schemas. Our middleware functions keep the same names, so our routes don’t need to change.

Start by adding the type imports to your middleware/user-validation.ts file:

import { z } from "zod";
import { Request, Response, NextFunction } from "express";

// Your three schemas here (requiredUserDataSchema, userIdSchema, partialUserDataSchema)

Updating the validateRequiredUserData Function

Let’s create the validateRequiredUserData function first, we’re using the same name we used in middleware/validation.ts:

export const validateRequiredUserData = (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const result = requiredUserDataSchema.safeParse(req.body);

  if (!result.success) {
    return res.status(400).json({
      error: "Validation failed",
      details: result.error.issues.map((issue) => issue.message),
    });
  }

  next();
};

How this works

  • requiredUserDataSchema.safeParse(req.body): Validates the request body against our schema - checks if the data matches the rules we defined.
  • safeParse(): Returns a result object with success/error information.
  • result.success: Boolean indicating if validation passed or failed.
  • result.error.issues: Array of validation error objects when validation fails.
  • .map((issue) => issue.message): Extracts just the error message from each error.
  • Return 400 with details: Sends specific validation errors back to the client - the details property will contain an array of validation error messages.
  • next(): Continues to the route handler if validation passes.

For example, if someone sends invalid data, they might receive:

{
  "error": "Validation failed",
  "details": [
    "Username must be at least 2 characters",
    "Email must be a valid email"
  ]
}

Updating the validateUserId Function

export const validateUserId = (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const result = userIdSchema.safeParse(req.params);

  if (!result.success) {
    return res.status(400).json({
      error: "Validation failed",
      details: result.error.issues.map((issue) => issue.message),
    });
  }

  next();
};

Key difference

  • req.params: Validates URL parameters instead of request body.
  • Same pattern otherwise: validate, check success, return errors or continue.

Updating the validatePartialUserData Function

This is almost identical to validateRequiredUserData, it just uses a different schema:

export const validatePartialUserData = (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const result = partialUserDataSchema.safeParse(req.body);

  if (!result.success) {
    return res.status(400).json({
      error: "Validation failed",
      details: result.error.issues.map((issue) => issue.message),
    });
  }

  next();
};

Only difference

  • Uses partialUserDataSchema instead of requiredUserDataSchema.


Repo link

Tags: