التحقق من الطلبات واستجابات JSON المقيّدة باستخدام Zod
حلّل أجسام الطلبات ومعلمات الاستعلام وتحقق منها، ثم أعد استجابات أخطاء مقيّدة ومنظمة جيدًا.
التحقق من الطلبات واستجابات JSON المقيّدة باستخدام Zod درس مجاني في Next.js 15 Fullstack (App Router + Server Actions) على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Next.js 15 Fullstack (App Router + Server Actions)، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Next.js 15 Fullstack (App Router + Server Actions) 4 دروس في المجموع.
بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.
Why Validate at the Edge?
In Next.js 15 Route Handlers (app/api/.../route.ts), the request body is just untyped JSON. TypeScript types vanish at runtime, so a client can send anything.
Zod lets you describe the expected shape once and get both:
- A runtime check that rejects bad input.
- A static type inferred from the schema, so your handler code is fully typed.
This lesson builds a typed POST handler that validates the body and query params, then returns clean, structured JSON errors.
Defining a Schema
Start by declaring the shape you expect. Zod schemas double as the single source of truth for both validation and types.
Use z.infer to derive a TypeScript type from the schema. There is no duplication: change the schema and the type updates automatically.
import { z } from 'zod';
export const CreateUserSchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email address'),
age: z.number().int().positive().optional(),
});
// Inferred type — fully typed, no duplication
export type CreateUserInput = z.infer<typeof CreateUserSchema>;Parsing the Request Body
Inside a Route Handler, read JSON with await request.json(). Wrap it in a try/catch because malformed JSON throws before Zod ever runs.
Use schema.safeParse() instead of parse(). safeParse never throws — it returns a result object you can branch on.
import { NextRequest, NextResponse } from 'next/server';
import { CreateUserSchema } from './schema';
export async function POST(request: NextRequest) {
let body: unknown;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ error: 'Invalid JSON body' },
{ status: 400 },
);
}
const result = CreateUserSchema.safeParse(body);
// ...handle result
}safeParse: Success vs Failure
safeParse returns a discriminated union:
- On success:
{ success: true, data }wheredatais the typed, validated value. - On failure:
{ success: false, error }whereerroris aZodError.
TypeScript narrows the type after you check result.success, so result.data is only accessible on the success branch.
const result = CreateUserSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ error: 'Validation failed', issues: result.error.issues },
{ status: 422 },
);
}
// result.data is now typed as CreateUserInput
const user = result.data;
console.log(user.email);Shaping a Useful Error Response
Dumping the raw ZodError works but is noisy. A cleaner API returns a flat map of field to messages. Zod's error.flatten() gives you fieldErrors and formErrors ready for the client.
Pick a consistent status: 400 for unparseable input, 422 (Unprocessable Entity) for well-formed JSON that fails validation.
if (!result.success) {
const { fieldErrors, formErrors } = result.error.flatten();
return NextResponse.json(
{
error: 'Validation failed',
fieldErrors, // { email: ['Invalid email address'] }
formErrors, // top-level errors
},
{ status: 422 },
);
}Validating Query Parameters
Query params arrive as strings via request.nextUrl.searchParams. Use z.coerce to convert and validate in one step — for example turning ?page=2 into a real number.
Provide .default() values so missing params do not break the handler.
import { z } from 'zod';
import { NextRequest, NextResponse } from 'next/server';
const QuerySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
});
export async function GET(request: NextRequest) {
const params = Object.fromEntries(request.nextUrl.searchParams);
const result = QuerySchema.safeParse(params);
if (!result.success) {
return NextResponse.json(
{ error: 'Invalid query', fieldErrors: result.error.flatten().fieldErrors },
{ status: 400 },
);
}
const { page, limit } = result.data; // numbers, defaulted
return NextResponse.json({ page, limit });
}Typed JSON Responses
NextResponse.json() is generic. Pass a type argument to lock down the success payload shape so your handler and your client stay in sync.
Define a shared response type and reuse it on both the server and the frontend fetch call.
import { NextResponse } from 'next/server';
type UserResponse = {
id: string;
name: string;
email: string;
};
function ok(user: UserResponse) {
// The generic enforces the body matches UserResponse
return NextResponse.json<UserResponse>(user, { status: 201 });
}A Reusable Validation Helper
Repeating the parse/branch logic in every handler gets tedious. Extract a small helper that validates a body against any schema and returns a typed result.
This keeps each Route Handler focused on business logic while centralizing the error-response format.
import { z } from 'zod';
import { NextResponse } from 'next/server';
export async function parseBody<T extends z.ZodTypeAny>(
request: Request,
schema: T,
): Promise<
| { ok: true; data: z.infer<T> }
| { ok: false; response: NextResponse }
> {
let raw: unknown;
try {
raw = await request.json();
} catch {
return { ok: false, response: NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) };
}
const result = schema.safeParse(raw);
if (!result.success) {
return {
ok: false,
response: NextResponse.json(
{ error: 'Validation failed', fieldErrors: result.error.flatten().fieldErrors },
{ status: 422 },
),
};
}
return { ok: true, data: result.data };
}Using the Helper in a Handler
With parseBody in place, a handler becomes short and readable. Validate, early-return on failure, then work with fully typed data.
This pattern scales: every endpoint follows the same shape, so error responses are consistent across your whole API.
import { NextRequest } from 'next/server';
import { CreateUserSchema } from './schema';
import { parseBody } from '@/lib/parse-body';
import { NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const parsed = await parseBody(request, CreateUserSchema);
if (!parsed.ok) return parsed.response;
const { name, email } = parsed.data; // typed
const user = { id: crypto.randomUUID(), name, email };
return NextResponse.json(user, { status: 201 });
}Transforming and Refining
Zod can do more than reject — it can normalize. Use .transform() to reshape values (trim, lowercase) and .refine() for cross-field rules that a single field check cannot express.
The output type reflects transforms, so downstream code sees the cleaned data.
import { z } from 'zod';
const SignupSchema = z
.object({
email: z.string().email().transform((s) => s.toLowerCase().trim()),
password: z.string().min(8),
confirm: z.string(),
})
.refine((d) => d.password === d.confirm, {
message: 'Passwords do not match',
path: ['confirm'],
});
type Signup = z.infer<typeof SignupSchema>;Validation Logic You Can Actually Run
Strip away Next.js and the core idea is pure data validation. Here is a self-contained TypeScript program that mirrors the same parse-and-branch flow using Zod, with no server involved.
It shows both a valid and an invalid payload and prints the structured error map.
import { z } from 'zod';
const Schema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.coerce.number().int().positive().optional(),
});
function validate(input: unknown) {
const result = Schema.safeParse(input);
if (!result.success) {
return { status: 422, body: { fieldErrors: result.error.flatten().fieldErrors } };
}
return { status: 201, body: result.data };
}
console.log(JSON.stringify(validate({ name: 'Ada', email: 'ada@example.com', age: '30' })));
console.log(JSON.stringify(validate({ name: '', email: 'nope' })));Quick Check
You are writing a POST Route Handler that validates the JSON body with a Zod schema. Which approach best returns a typed, structured error response without crashing on bad input?
Recap
You built a typed, validated Next.js 15 API endpoint:
- Schema first: define a Zod schema and derive the type with
z.infer. - safeParse, not parse: branch on
result.successso nothing throws. - Structured errors: use
error.flatten(); return 400 for bad JSON, 422 for validation failures. - Query params: coerce strings with
z.coerceand supply.default()values. - Typed responses:
NextResponse.json<T>()keeps server and client in sync. - Reuse: a
parseBodyhelper centralizes the parse/error pattern across every handler.
The result is an edge API that is safe at runtime and fully typed at compile time.
الأسئلة الشائعة
هل درس «التحقق من الطلبات واستجابات JSON المقيّدة باستخدام Zod» مجاني؟
نعم — نص درس «التحقق من الطلبات واستجابات JSON المقيّدة باستخدام Zod» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Next.js 15 Fullstack (App Router + Server Actions)، انتقل إلى CoddyKit PRO. تتضمن دورة Next.js 15 Fullstack (App Router + Server Actions) 4 دروس في المجموع.
ماذا ستتعلم في «التحقق من الطلبات واستجابات JSON المقيّدة باستخدام Zod»؟
حلّل أجسام الطلبات ومعلمات الاستعلام وتحقق منها، ثم أعد استجابات أخطاء مقيّدة ومنظمة جيدًا. تتمرن على Next.js 15 Fullstack (App Router + Server Actions) مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Next.js 15 Fullstack (App Router + Server Actions)؟
لا تُشترط خبرة سابقة. Next.js 15 Fullstack (App Router + Server Actions) على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «التحقق من الطلبات واستجابات JSON المقيّدة باستخدام Zod»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Next.js 15 Fullstack (App Router + Server Actions) هذا؟
نعم. كل درس في Next.js 15 Fullstack (App Router + Server Actions) يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تصميم معالجات المسارات RESTful باستخدام Web Request API
- المفاضلة بين Node Runtime وEdge Runtime
- استجابات البث وReadableStream في المعالجات
- التحقق من الطلبات واستجابات JSON المقيّدة باستخدام Zod