Loading...
Loading...
> Validate all inputs with Zod schemas for type-safe, secure data handling.
Schema validation ensures all data entering your application is properly validated and typed. Zod provides runtime validation with automatic TypeScript type inference.
Define schemas to validate data
1// src/lib/validations/user.ts23import { z } from "zod";45export const userSchema = z.object({6 email: z7 .string()8 .email("Invalid email address")9 .min(1, "Email is required"),10 password: z11 .string()12 .min(8, "Password must be at least 8 characters")13 .regex(14 /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,15 "Password must contain uppercase, lowercase, and number"16 ),17 name: z18 .string()19 .min(2, "Name must be at least 2 characters")20 .max(100, "Name must be less than 100 characters"),21});2223// Infer TypeScript type from schema24export type UserInput = z.infer<typeof userSchema>;
Validate request bodies in API routes
1// src/app/api/users/route.ts23import { NextRequest, NextResponse } from "next/server";4import { z } from "zod";56const createUserSchema = z.object({7 email: z.string().email(),8 name: z.string().min(1).max(100),9 role: z.enum(["user", "admin"]).default("user"),10});1112export async function POST(request: NextRequest) {13 try {14 const body = await request.json();1516 // Validate request body17 const result = createUserSchema.safeParse(body);1819 if (!result.success) {20 return NextResponse.json(21 {22 error: "Validation failed",23 details: result.error.flatten().fieldErrors,24 },25 { status: 400 }26 );27 }2829 // result.data is fully typed30 const { email, name, role } = result.data;3132 // Create user with validated data33 // ...3435 return NextResponse.json({ success: true });36 } catch (_) {37 return NextResponse.json(38 { error: "Invalid JSON body" },39 { status: 400 }40 );41 }42}
Reusable validation patterns for common fields
1// src/lib/validations/common.ts23import { z } from "zod";45// Email validation6export const emailSchema = z7 .string()8 .email("Invalid email")9 .toLowerCase()10 .trim();1112// Password with strength requirements13export const passwordSchema = z14 .string()15 .min(8, "Minimum 8 characters")16 .max(128, "Maximum 128 characters")17 .regex(/[a-z]/, "Must contain lowercase letter")18 .regex(/[A-Z]/, "Must contain uppercase letter")19 .regex(/[0-9]/, "Must contain number")20 .regex(/[^a-zA-Z0-9]/, "Must contain special character");2122// UUID validation23export const uuidSchema = z.string().uuid("Invalid ID format");2425// URL validation26export const urlSchema = z.string().url("Invalid URL").optional();2728// Phone number (basic)29export const phoneSchema = z30 .string()31 .regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number");3233// Date validation34export const dateSchema = z.coerce.date();3536// Pagination37export const paginationSchema = z.object({38 page: z.coerce.number().int().positive().default(1),39 limit: z.coerce.number().int().min(1).max(100).default(20),40});4142// Sort order43export const sortSchema = z.object({44 sortBy: z.string().optional(),45 order: z.enum(["asc", "desc"]).default("desc"),46});
Use schemas with react-hook-form
1"use client";23import { useForm } from "react-hook-form";4import { zodResolver } from "@hookform/resolvers/zod";5import { z } from "zod";67const contactSchema = z.object({8 name: z.string().min(1, "Name is required"),9 email: z.string().email("Invalid email"),10 message: z.string().min(10, "Message must be at least 10 characters"),11});1213type ContactForm = z.infer<typeof contactSchema>;1415export function ContactForm() {16 const {17 register,18 handleSubmit,19 formState: { errors },20 } = useForm<ContactForm>({21 resolver: zodResolver(contactSchema),22 });2324 const onSubmit = (data: ContactForm) => {25 // data is fully validated and typed26 // Process validated data27 };2829 return (30 <form onSubmit={handleSubmit(onSubmit)}>31 <div>32 <input {...register("name")} placeholder="Name" />33 {errors.name && (34 <p className="text-destructive">{errors.name.message}</p>35 )}36 </div>3738 <div>39 <input {...register("email")} placeholder="Email" />40 {errors.email && (41 <p className="text-destructive">{errors.email.message}</p>42 )}43 </div>4445 <div>46 <textarea {...register("message")} placeholder="Message" />47 {errors.message && (48 <p className="text-destructive">{errors.message.message}</p>49 )}50 </div>5152 <button type="submit">Send</button>53 </form>54 );55}
Validate URL search params
1// src/app/api/items/route.ts23import { NextRequest, NextResponse } from "next/server";4import { z } from "zod";56const querySchema = z.object({7 page: z.coerce.number().int().positive().default(1),8 limit: z.coerce.number().int().min(1).max(100).default(20),9 search: z.string().optional(),10 status: z.enum(["active", "inactive", "all"]).default("all"),11});1213export async function GET(request: NextRequest) {14 const searchParams = request.nextUrl.searchParams;1516 // Convert searchParams to object17 const params = Object.fromEntries(searchParams.entries());1819 // Validate20 const result = querySchema.safeParse(params);2122 if (!result.success) {23 return NextResponse.json(24 { error: "Invalid query parameters" },25 { status: 400 }26 );27 }2829 const { page, limit, search, status } = result.data;3031 // Use validated params for database query32 // ...33}
Validate environment variables at startup
1// src/lib/env.ts23import { z } from "zod";45const envSchema = z.object({6 // Required7 DATABASE_URL: z.string().url(),8 NEXTAUTH_SECRET: z.string().min(32),9 NEXTAUTH_URL: z.string().url(),1011 // Optional with defaults12 NODE_ENV: z13 .enum(["development", "production", "test"])14 .default("development"),1516 // Conditional (required in production)17 STRIPE_SECRET_KEY: z.string().startsWith("sk_").optional(),18 STRIPE_WEBHOOK_SECRET: z.string().startsWith("whsec_").optional(),19});2021// Validate at build/startup time22const parsed = envSchema.safeParse(process.env);2324if (!parsed.success) {25 console.error("❌ Invalid environment variables:");26 console.error(parsed.error.flatten().fieldErrors);27 throw new Error("Invalid environment variables");28}2930export const env = parsed.data;
Transform and sanitize data during validation
1import { z } from "zod";23const userInputSchema = z.object({4 // Trim and lowercase email5 email: z6 .string()7 .email()8 .transform((val) => val.toLowerCase().trim()),910 // Sanitize name11 name: z12 .string()13 .transform((val) => val.trim().replace(/\s+/g, " ")),1415 // Parse JSON string16 metadata: z17 .string()18 .transform((val, ctx) => {19 try {20 return JSON.parse(val);21 } catch {22 ctx.addIssue({23 code: z.ZodIssueCode.custom,24 message: "Invalid JSON",25 });26 return z.NEVER;27 }28 }),2930 // Convert string to array31 tags: z32 .string()33 .transform((val) => val.split(",").map((s) => s.trim())),34});