Skip to main content
•5 min read

Zod ile API Validation: Boundary Katmanını Doğru Tasarlamak

Mustafa Kürşad BaşerMustafa Kürşad BAŞER
••
Zod ile API Validation: Boundary Katmanını Doğru Tasarlamak

TypeScript Yetiyor mu? Gerçek Hayatta Pek Değil.

TypeScript kullanmaya başladıktan sonra çoğumuz benzer bir rahatlığa kapıldık: "Artık type-safe’im." Kod compile ediyorsa içimiz rahat. Ancak production ortamında kırılan sistemlere baktığınızda problemin çoğu zaman tip tanımlarından değil, runtime’da doğrulanmamış verilerden kaynaklandığını görürsünüz.

HTTP istekleri, üçüncü parti servisler, webhook’lar, hatta environment variable’lar. Bunların hepsi sisteminizin dışarıya açılan yüzeyi ve TypeScript oralarda devrede değildir. Derleme bittiği anda tipler buharlaşır, geriye sade JavaScript kalır. Zod tam bu boşluğu doldurur.

Zod Nedir ve Neden Bu Kadar Popüler Oldu?

Zod, çalışma zamanında doğrulama yapan bir şema kütüphanesi. Onu benzerlerinden ayıran şey tek kaynak ilkesi. Şemayı bir kez yazıyorsunuz, TypeScript tipi de aynı şemadan türüyor. Yani tip ile doğrulama kuralı asla birbirinden ayrı düşmüyor.

Örneğin basit bir kullanıcı oluşturma isteğini ele alalım:

ts
import { z } from "zod"; const CreateUserSchema = z.object({  email: z.string().email(),  age: z.number().int().min(18),}); type CreateUserRequest = z.infer<typeof CreateUserSchema>;

Tek bir tanımdan hem çalışma zamanı doğrulaması hem de derleme zamanı tipi çıkıyor. Şemaya yeni bir alan eklediğinizde tip kendiliğinden güncelleniyor; ikisinin birbirinden kopma ihtimali ortadan kalkıyor.

Validation’ı Controller İçine Gömme Hatası

Projelerde sık gördüğüm bir anti-pattern var: Controller içinde manuel kontrol yazmak.

ts
if (!req.body.email) {  return res.status(400).send("Email required");}

İlk bakışta masum. Ama beşinci uç noktada aynı e-posta kontrolü üç ayrı dosyada, üç ayrı şekilde yazılmış oluyor. Biri boşlukları kırpıyor, diğeri kırpmıyor. Hata mesajları tutmuyor. Kural nerede diye baktığınızda cevap "her yerde ve hiçbir yerde" çıkıyor.

Daha sağlıklı yaklaşım: Validation’ı boundary’de, merkezi ve deklaratif bir şekilde yapmak.

ts
const result = CreateUserSchema.safeParse(req.body); if (!result.success) {  return res.status(400).json({    errors: result.error.flatten(),  });} const data = result.data;

safeParse istisna fırlatmaz, sonucu bir nesne olarak döndürür. Böylece hata dalını açıkça ele almak zorunda kalırsınız. parse de kullanılabilir ama o istisna fırlatır, dolayısıyla bir hata yakalayıcıya ihtiyaç duyar.

DTO ile Domain Model’i Ayırmak Neden Önemli?

Zod kullanırken yapılan bir diğer hata, DTO şemasını doğrudan domain modeli gibi kullanmaktır. Oysa transport katmanı ile domain katmanı farklı sorumluluklara sahiptir.

Örneğin API’ye gelen veri string olabilir ama domain tarafında Date nesnesine ihtiyaç duyabilirsiniz. Bu noktada transform devreye girer.

ts
const CreateOrderSchema = z.object({  price: z.string().transform((val) => Number(val)),  createdAt: z.string().transform((val) => new Date(val)),});

Veri sınırda normalize edildiği için iş mantığı tarafında "acaba string mi geldi" diye savunma kodu yazmanız gerekmiyor. z.infer çıktısı da dönüşüm sonrası tipi verir, yani price orada number görünür.

Discriminated Union ile Illegal State’leri Engellemek

Özellikle ödeme, sipariş veya job state yönetimi gibi alanlarda Zod’un discriminatedUnion özelliği oldukça işe yarar.

ts
const PaymentSchema = z.discriminatedUnion("status", [  z.object({ status: z.literal("pending") }),  z.object({ status: z.literal("completed"), transactionId: z.string() }),  z.object({ status: z.literal("failed"), reason: z.string() }),]);

Bu yaklaşım, "completed ama transactionId yok" gibi illegal state’leri daha en başta engeller. Type narrowing sayesinde switch-case blokları da güvenli hale gelir.

Frontend & Backend Ortak Şema Kullanımı

Monorepo yapılarında şemaları ortak bir pakette toplamak ciddi avantaj sağlıyor. Next.js projelerinde API route’lar ile frontend formları aynı şemayı paylaşabilir; kural bir yerde tanımlanır, iki tarafta birden geçerli olur.

tRPC kullanan ekiplerde ise Zod neredeyse varsayılan hale geldi. Tek kaynak, çift taraflı tip güvenliği demek.

Performans Konusu: Abartılıyor mu?

Çoğu CRUD uygulamasında Zod’un maliyeti ölçüm hatası kadar kalır. Fark hissedeceğiniz yerler belli: çok büyük diziler, derin iç içe nesneler ve uzun refine zincirleri. Böyle bir şüpheniz varsa tahmin yürütmeyin, ölçün.

En doğru yaklaşım: Validation’ı boundary’de yapmak ve aynı veriyi sistem içinde tekrar tekrar parse etmemek. Zod bir güvenlik katmanıdır, veri işleme motoru değil.

Sık Yapılan Hatalar

  • Business logic’i schema içine gömmek
  • Tek ve devasa bir mega-schema oluşturmak
  • Validation sonucunu kontrol etmeden data’yı kullanmak
  • Sadece frontend validation’a güvenmek
  • DTO ile domain model’i karıştırmak

Sonuç

Zod’u yalnızca bir validation kütüphanesi olarak görmek eksik bir yaklaşım olur. Asıl değer, onu sisteminizin giriş noktalarına bilinçli bir şekilde konumlandırdığınızda ortaya çıkar.

Runtime’da doğrulanmamış her veri, teknik borcun görünmeyen bir parçasıdır. Küçük projelerde tolere edilebilir gibi görünse de, sistem büyüdükçe bu borç faizle geri döner.

Boundary’de net validation, katmanlı mimari ve illegal state’leri erken engelleme yaklaşımı; daha öngörülebilir, daha test edilebilir ve daha güvenilir sistemler üretmenizi sağlar. Zod burada yalnızca bir araçtır. Asıl farkı yaratan, onu nerede ve nasıl kullandığınızdır.

Hâlâ controller içinde dağınık validasyonlar yazıyorsanız, küçük bir refactor ile başlayın. Tek bir endpoint’i doğru tasarlamak bile sistemin genel kalitesini yukarı çeker.

Kod da süreçler gibi evriliyor. Mesele onu bilinçli bir mimari karar çerçevesinde evrimleştirmek.

Share this post

Link copied!
Mustafa Kürşad Başer
Author

Mustafa Kürşad Başer

Senior Software Engineer

A passionate software engineer who enjoys creating elegant solutions to complex problems. Beyond coding, I am deeply interested in exploring the intersections of technology, art, and human consciousness.