Zod ile API Validation: Boundary Katmanını Doğru Tasarlamak
Mustafa Kürşad BAŞER
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 request’leri, üçüncü parti servisler, webhook’lar, hatta environment variable’lar… Bunların tamamı sisteminiz için potansiyel risk alanıdır. TypeScript burada devrede değildir. İşte tam bu noktada devreye girer.
Zod Nedir ve Neden Bu Kadar Popüler Oldu?
Zod, runtime validation yapan ve aynı şemadan TypeScript tiplerini üretebilen bir schema validation kütüphanesidir. Asıl gücü ise API’sinin sade olması ve type inference ile doğal uyum içinde çalışmasıdır.
Örneğin basit bir kullanıcı oluşturma isteğini ele alalım:
1import { z } from "zod";
2
3const CreateUserSchema = z.object({
4 email: z.string().email(),
5 age: z.number().int().min(18),
6});
7
8type CreateUserRequest = z.infer<typeof CreateUserSchema>;Bu yapı iki kritik problemi aynı anda çözer: Runtime validation ve compile-time type üretimi. DTO ile type arasında drift oluşma ihtimali ciddi şekilde azalır.
Validation’ı Controller İçine Gömme Hatası
Projelerde sık gördüğüm bir anti-pattern var: Controller içinde manuel kontrol yazmak.
1if (!req.body.email) {
2 return res.status(400).send("Email required");
3}Başta masum görünür. Ama birkaç endpoint sonra kontrol mantığı dağılmaya başlar. Aynı validasyon farklı yerlerde farklı şekillerde yazılır. Tutarsızlık oluşur.
Daha sağlıklı yaklaşım: Validation’ı boundary’de, merkezi ve deklaratif bir şekilde yapmak.
1const result = CreateUserSchema.safeParse(req.body);
2
3if (!result.success) {
4 return res.status(400).json({
5 errors: result.error.flatten(),
6 });
7}
8
9const data = result.data;Bu yapı okunabilir, test edilebilir ve tekrar kullanılabilir bir çözüm sunar.
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.
1const CreateOrderSchema = z.object({
2 price: z.string().transform((val) => Number(val)),
3 createdAt: z.string().transform((val) => new Date(val)),
4});Bu sayede boundary’de normalize edilmiş veri elde edilir. Business logic tarafında "string mi number mı?" gibi savunma kodları yazmak zorunda kalmazsınız.
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.
1const PaymentSchema = z.discriminatedUnion("status", [
2 z.object({ status: z.literal("pending") }),
3 z.object({ status: z.literal("completed"), transactionId: z.string() }),
4 z.object({ status: z.literal("failed"), reason: z.string() }),
5]);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 Zod şemalarını shared bir paket altında toplamak ciddi avantaj sağlar. Özellikle projelerinde API route’lar ile frontend formlar arasında aynı şemayı kullanmak mümkün.
Benzer şekilde kullanan ekiplerde Zod neredeyse doğal bir standart haline gelmiş durumda. Tek kaynak, çift taraflı type safety demektir.
Performans Konusu: Abartılıyor mu?
Çoğu CRUD uygulamasında Zod’un performans maliyeti ihmal edilebilir düzeydedir. Ancak çok büyük payload’lar veya yoğun refine zincirleri söz konusuysa validation maliyeti artabilir.
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.
Eğer projelerinizde hâlâ controller içinde dağınık validasyonlar yazıyorsanız, küçük bir refactor ile başlayın. Tek bir endpoint’i bile doğru tasarlamak, sisteminizin genel kalitesini yukarı çekecektir. Süreçler gibi kod da evrilir. Önemli olan, onu bilinçli bir mimari karar çerçevesinde evrimleştirmektir.

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.

