In our previous blogs, we built:
A structured API response helper →
serverResponse
Read hereAn async error-handling wrapper →
serverAsyncResolve
Read here
Now, let’s enhance API validation in Next.js 15 using Zod and TypeScript.
Why Use Zod for API Validation?
🚀 Zod is a TypeScript-first schema validation library that helps ensure API inputs are correctly formatted before processing.
✅ Eliminates manual validation – No need for if-else checks.
✅ Type-safe validation – Prevents unexpected runtime errors.
✅ Structured error messages – Helps frontend developers display form errors correctly.
Step 1: Define the Validation Schema
Let’s create a validation schema for a password reset request using Zod:
import { z } from "zod";
const emailValidation = z
.string({ required_error: "Email address is required" })
.email({ message: "Email address is required" })
.max(80, "Email address is too long");
export const forgetPasswordSchema = z.object({
email: emailValidation,
});
Explanation:
✔️ Requires an email → Users must enter a valid email address.
✔️ Ensures correct format → Rejects malformed email addresses.
✔️ Limits length → Prevents excessively long input.
Step 2: Handle API Requests with Validation
Now, let’s create a POST API route for password reset that:
- Validates the input with Zod
- Uses our
serverAsyncResolve
wrapper for error handling - Sends structured responses with
serverResponse
import { forgetPasswordSchema } from "@/schemas/forget-password";
import { serverAsyncResolve } from "@/helpers/server-helper/serverAsyncResolve";
import { serverResponse } from "@/helpers/server-helper/serverResponse";
import { getJsonBodyData } from "@/helpers/server-helper/getJsonBodyData";
export async function POST(req: Request) {
return serverAsyncResolve(async () => {
const body = await getJsonBodyData<ForgetPasswordType>(req);
const { email } = forgetPasswordSchema.parse(body);
return serverResponse({
success: true,
message: "Reset password email sent",
});
});
}
Uses getJsonBodyData
Read previous blog to extract JSON input.
🔹 Validates input using forgetPasswordSchema.parse(body)
.
🔹 If validation fails, it throws a ZodError
, which is handled globally.
Step 3: Handling Validation Errors Globally
Instead of handling validation errors inside each API, let’s centralize error handling in serverAsyncResolve
:
import type { NextResponse } from "next/server";
import type { ServerResponseType } from "@/types";
import type { ZodError } from "zod";
import validationErrors from "@/helpers/server-helper/validationErrors";
import serverResponse from "@/helpers/server-helper/serverResponse";
export default async function serverAsyncResolve<T>(
asyncCallback: () => Promise<NextResponse<ServerResponseType<T>>>,
): Promise<NextResponse<ServerResponseType<T>>> {
try {
return await asyncCallback();
} catch (err) {
if (err instanceof ZodError) {
return serverResponse({
success: false,
error: validationErrors(err),
status: 422,
});
}
if (err instanceof Error) {
return serverResponse({
success: false,
error: err.message,
status: 500,
});
}
return serverResponse({
success: false,
error: "Something went wrong",
status: 500,
});
}
}
📌 This ensures all Zod validation errors return a structured response.
Step 4: Convert Zod Errors into Readable Messages
To make Zod errors more user-friendly, we transform them into an array of ValidationError
objects:
import type { ValidationError } from "@/types";
import type { ZodError } from "zod";
export default function validationErrors(error: ZodError): ValidationError[] {
return error.errors.map((err) => ({
field: err.path.join("."),
message: err.message,
}));
}
✔️ Now, frontend applications receive structured errors like:
{
"success": false,
"error": [
{ "field": "email", "message": "Email address is required" }
],
"status": 422
}
Final Thoughts
🎯 By combining Next.js 15, Zod, and TypeScript, we achieve:
✅ Automatic request validation with Zod.
✅ Centralized error handling with serverAsyncResolve
.
✅ Consistent API responses with serverResponse
.
💡 Ready to implement validation in your Next.js APIs? Drop a comment and let me know how you handle validation!
Top comments (0)