Zod ile İstek Doğrulama ve Türlendirilmiş JSON Yanıtları
İstek gövdelerini ve sorgu parametrelerini ayrıştırıp doğrulayın; türlendirilmiş, iyi yapılandırılmış hata yanıtları döndürün.
Zod ile İstek Doğrulama ve Türlendirilmiş JSON Yanıtları, CoddyKit'te ücretsiz bir Next.js 15 Fullstack (App Router + Server Actions) dersidir. Bu, 4 dersinin 4. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Next.js 15 Fullstack (App Router + Server Actions) öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Next.js 15 Fullstack (App Router + Server Actions) kursu toplamda 4 dersten oluşur.
Bu dersin bazı bölümleri henüz çevrilmemiş olup İngilizce olarak gösterilmektedir.
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.
Yapay zeka eğitmeniyle TypeScript öğren — ücretsiz
Tarayıcında gerçek kod yaz ve çalıştır, 7/24 yapay zeka eğitmeninden anında yardım al; web'de ya da uygulamada kaldığın yerden devam et.
- Kurslar
- 22
- Dersler
- 88
Sıkça Sorulan Sorular
“Zod ile İstek Doğrulama ve Türlendirilmiş JSON Yanıtları” dersi ücretsiz mi?
Evet — “Zod ile İstek Doğrulama ve Türlendirilmiş JSON Yanıtları” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Next.js 15 Fullstack (App Router + Server Actions) kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Next.js 15 Fullstack (App Router + Server Actions) kursu toplamda 4 dersten oluşur.
“Zod ile İstek Doğrulama ve Türlendirilmiş JSON Yanıtları” dersinde ne öğreneceğim?
İstek gövdelerini ve sorgu parametrelerini ayrıştırıp doğrulayın; türlendirilmiş, iyi yapılandırılmış hata yanıtları döndürün. Next.js 15 Fullstack (App Router + Server Actions) ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.
Next.js 15 Fullstack (App Router + Server Actions) öğrenmeye başlamak için deneyim gerekli mi?
Önceden deneyim gerekmez. CoddyKit'te Next.js 15 Fullstack (App Router + Server Actions), başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 4. dersidir.
“Zod ile İstek Doğrulama ve Türlendirilmiş JSON Yanıtları” dersi ne kadar sürer?
Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.
Bu Next.js 15 Fullstack (App Router + Server Actions) dersinde kod yazıp çalıştırabilir miyim?
Evet. Her Next.js 15 Fullstack (App Router + Server Actions) dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.
Bu kursun tüm dersleri
- Web İstek API'siyle RESTful Rota İşleyicileri Tasarlama
- Node Çalışma Zamanı ve Edge Çalışma Zamanı Arasındaki Ödünleşimler
- İşleyicilerde Yanıt Akışı ve ReadableStream
- Zod ile İstek Doğrulama ve Türlendirilmiş JSON Yanıtları