Tutorial

Tutorial: Validate API requests with Zod and TypeScript

February 22, 2026 · Kabim Tech Engineering · 2 min read

← Back to blog
TypeScript code on a developer screen
TypeScript code on a developer screen
Zod validation with TypeScript (beginner walkthrough)
  • Typescript
  • Api

Define Zod schemas, infer types, and return clean 422 errors from Next.js Route Handlers. Copy-paste ready examples included.

Untyped JSON is where production bugs hide. Zod gives you runtime validation and TypeScript types from a single schema, perfect for Route Handlers and Server Actions.

What you will build

  • A reusable `parseJson` helper
  • A Zod schema for a job application or contact payload
  • A POST route that returns field-level errors on invalid input

Prerequisites

  • Next.js App Router project
  • `npm install zod`
  • Familiarity with REST status codes (400, 422, 500)

Step-by-step

  1. Create `lib/schemas/job-application.ts` with a Zod object schema for name, email, phone, and coverLetter.
  2. Export the inferred type: `export type JobApplicationInput = z.infer<typeof jobApplicationSchema>`.
  3. Add `lib/http/parse-json.ts` that reads `request.json()`, runs `schema.safeParse`, and returns `{ data }` or `{ error }`.
  4. In `app/api/careers/apply/route.ts`, call the parser; on failure return `NextResponse.json({ fields: ... }, { status: 422 })`.
  5. On success, persist to database or file and return `{ ok: true }`.
  6. Add unit tests for the schema with valid and invalid payloads.

Schema definition

import { z } from "zod";

export const jobApplicationSchema = z.object({
  name: z.string().trim().min(2, "Name is required."),
  email: z.string().trim().email("Enter a valid email."),
  phone: z.string().trim().min(8, "Enter a valid phone number."),
  coverLetter: z.string().trim().min(40, "Cover letter is too short."),
  jobSlug: z.string().trim().min(1),
});

export type JobApplicationInput = z.infer<typeof jobApplicationSchema>;

Route Handler

import { NextResponse } from "next/server";
import { jobApplicationSchema } from "@/lib/schemas/job-application";

export async function POST(request: Request) {
  const json = await request.json().catch(() => null);
  const parsed = jobApplicationSchema.safeParse(json);

  if (!parsed.success) {
    return NextResponse.json(
      { error: "Validation failed.", fields: parsed.error.flatten().fieldErrors },
      { status: 422 },
    );
  }

  // save parsed.data
  return NextResponse.json({ ok: true });
}

Common mistakes

  • Validating only on the client, always re-validate on the server
  • Using `.parse()` in routes, prefer `safeParse` for friendly API errors
  • Forgetting to trim strings before min-length checks

Next steps

Share schemas between client forms (React Hook Form + `@hookform/resolvers/zod`) and server routes so validation rules never drift.

Want to talk about your product?

Need a dedicated squad or a fixed-scope MVP? We help teams ship on time.