Bu makalede7 bölüm
- Teknoloji
- TypeScript, Zod, React Hook Form, Next.js Route Handlers, HTTP 422
- Konu
- Şema tabanlı istemci ve sunucu form doğrulaması
- Seviye
- Orta
- Ön koşullar
- TypeScript ve React temel bilgisi
- REST API ile form gönderimi deneyimi
İstemcinin kabul ettiği değeri sunucu reddeder; kullanıcı “Kaydet”e basar ve açıklamasız bir “İşlem başarısız” görür. Ya da tersi olur: sunucu, istemcinin hiç kontrol etmediği bir alanı sessizce kabul eder ve hatalı kayıt aylarca raporları bozar. Kurumsal iş formlarında doğrulama kuralları genellikle üç yerde yaşar: tarayıcıdaki form kodu, API denetleyicisi ve veritabanı kısıtları. Zamanla birbirinden ayrışırlar. Aşağıda TypeScript kullanan web uygulamalarında kuralları tek şemada toplamayı, asenkron ve alanlar arası kuralları, çok adımlı formları ve sunucu hatalarının kullanıcıya gösterilmesini kod üzerinden anlatıyoruz.
İstemci ve sunucu doğrulamasının rolleri
İki katmanın amacı farklıdır ve biri diğerinin yerine geçmez:
| Katman | Amaç | Güvenilir mi? |
|---|---|---|
| İstemci | Hızlı geri bildirim, gereksiz istekten kaçınma, kullanıcı deneyimi | Hayır; atlatılabilir |
| Sunucu (uygulama) | Veri bütünlüğü, iş kuralları, yetki kontrolü | Evet; tek bağlayıcı karar noktası |
| Veritabanı | Son savunma hattı: NOT NULL, UNIQUE, CHECK, yabancı anahtar | Evet; yarış koşullarına karşı da korur |
İstemci doğrulaması bir güvenlik önlemi değildir. İstek tarayıcı geliştirici araçlarıyla, curl ile veya bir otomasyon betiğiyle doğrudan API’ye gönderilebilir; JavaScript kapatılabilir, alanlar DOM’da değiştirilebilir. Tarayıcı sizin değil, kullanıcının makinesidir. Sunucu, istemciden gelen her alanı hiç doğrulanmamış gibi yeniden kontrol eder. Sunucu doğrulaması yalnız biçim kontrolü de değildir: seçilen departmanId’nin gerçekten var olması ve kullanıcının o departmana kayıt açma yetkisi de sunucunun işidir. Girdi doğrulamasının güvenlik boyutu için OWASP Input Validation Cheat Sheet iyi bir başvuru kaynağı; kötü niyetli otomatik gönderimlere karşı önlemler ise web formlarında spam önleme rehberinin konusu.
Şema tabanlı doğrulama yaklaşımı
Şema tabanlı doğrulamada form verisinin yapısı ve kuralları bir kez, bildirimsel olarak tanımlanır; aynı şema hem tarayıcıda hem sunucuda çalışır, TypeScript tipi de şemadan türetilir. Ekosistemde Zod, Valibot ve Yup bu iş için kullanılır. Örnekler Zod ile yazıldı; ana sürümler arasında bazı API ayrıntıları değiştiği için kullandığınız sürümün Zod dokümantasyonunu kontrol edin.
// shared/schemas/satinalma.ts — istemci ve sunucunun ortak kullandığı modül
import { z } from 'zod';
export const satinalmaTalebi = z.object({
baslik: z.string().trim().min(5, 'Başlık en az 5 karakter olmalı').max(120),
tutar: z.coerce.number().positive('Tutar sıfırdan büyük olmalı'),
paraBirimi: z.enum(['TRY', 'EUR', 'USD']),
maliyetMerkezi: z.string().regex(/^[A-Z]{2}-\d{4}$/, 'Biçim: AA-1234'),
teslimTarihi: z.coerce.date(),
aciklama: z.string().trim().max(2000).optional(),
});
export type SatinalmaTalebi = z.infer<typeof satinalmaTalebi>;
Pratik sonuçları şunlar. z.infer ile türetilen tip API istemcisinde, form bileşeninde ve sunucu işleyicisinde ortak kullanılır; bir alan eklenince derleyici eksik yerleri gösterir. trim() gibi dönüşümler veriyi normalleştirir, sunucuya hep aynı biçimde ulaşır. z.coerce form alanlarından metin olarak gelen değeri sayıya veya tarihe çevirir. Burada bir tuzak var: boş metin Number('') ile 0’a dönüşür, bu yüzden zorunlu sayısal alanlarda boş değeri ayrıca ele alın. Kontrol edilmezse sıfır tutarlı talep kaydı “geçerli” görünür.
Sunucu tarafında aynı şema istek gövdesini doğrular. Doğrulanmamış gövde iş mantığına asla geçmez:
// Sunucu: Next.js Route Handler örneği
import { satinalmaTalebi } from '@/shared/schemas/satinalma';
export async function POST(req: Request) {
const sonuc = satinalmaTalebi.safeParse(await req.json());
if (!sonuc.success) {
const alanHatalari: Record<string, string[]> = {};
for (const issue of sonuc.error.issues) {
const alan = issue.path.join('.') || '_form';
(alanHatalari[alan] ??= []).push(issue.message);
}
return Response.json({ alanHatalari }, { status: 422 });
}
const talep = sonuc.data; // tipli ve normalleştirilmiş veri
// yetki kontrolü + iş kuralları + kayıt
}
İstemcide React Hook Form, @hookform/resolvers paketindeki zodResolver ile aynı şemayı kullanır. Şemalar ayrı bir modülde durduğu için sunucuya özgü bağımlılıkların (veritabanı istemcisi, gizli anahtarlar) istemci paketine sızmaması kolay denetlenir: shared/schemas altındaki dosyaların yalnız zod ve diğer şema dosyalarını içe aktarmasına izin veren bir lint kuralı bu sınırı korur. Next.js Server Actions kullanıyorsanız aynı şema Route Handler yerine eylem fonksiyonunun başında çalışır; akış değişmez.
Asenkron ve alanlar arası kurallar
Bazı kurallar tek alana sığmaz. Bitiş tarihinin başlangıçtan sonra olması, “Diğer” seçilince açıklamanın zorunlu olması veya belirli bir tutarın üzerinde ikinci onaylayıcı istenmesi alanlar arası kurallardır. Zod’da bunlar nesne düzeyinde refine ile yazılır ve hatanın bağlanacağı alan path ile belirtilir:
export const izinTalebi = z.object({
baslangic: z.coerce.date(),
bitis: z.coerce.date(),
tur: z.enum(['yillik', 'mazeret', 'diger']),
aciklama: z.string().trim().optional(),
})
.refine((d) => d.bitis >= d.baslangic, {
message: 'Bitiş tarihi başlangıçtan önce olamaz',
path: ['bitis'],
})
.refine((d) => d.tur !== 'diger' || (d.aciklama?.length ?? 0) > 0, {
message: '“Diğer” seçildiğinde açıklama zorunludur',
path: ['aciklama'],
});
Asenkron kurallar (vergi numarasının sistemde kayıtlı olup olmadığı, kullanıcı adının boşta olup olmadığı) sunucuya istek ister. İstemcide bunlar yalnız kullanıcı deneyimi içindir: alan odaktan çıkınca (onBlur) ve kısa bir gecikmeyle (debounce) çalışır, her tuş vuruşunda istek atılmaz. Kesin kontrol gönderim anında sunucuda tekrarlanır, çünkü iki kontrol arasında durum değişmiş olabilir. Tekillik gibi kurallarda son güvence veritabanındaki UNIQUE kısıtıdır; uygulama kısıt ihlali hatasını yakalayıp ilgili alana anlamlı bir mesajla eşlemelidir. Ham SQL hatası kullanıcıya gitmez.
Sahada sık karşılaşılan iki davranışı bilin. Birincisi: nesne düzeyindeki refine, alanlardan birinde tip düzeyinde hata (eksik zorunlu alan, geçersiz tarih gibi) varken çalışmaz; kullanıcı tarih sırası hatasını ancak bu alanları düzelttikten sonra görür. Bu kabul edilemiyorsa kontrolü istemcide ayrıca tetikleyin veya alanları tek bir alt nesnede toplayın. İkincisi: asenkron refine içeren bir Zod şeması parseAsync veya safeParseAsync ile çalıştırılır; senkron parse bu durumda hata fırlatır.
Çok adımlı formlarda durum ve doğrulama
Özel web uygulamalarında tedarikçi kaydı, personel işe alım formu veya teklif hazırlama gibi uzun süreçler çok adımlı sihirbazlara bölünür. Üç tasarım kararı işin gidişini belirler:
- Şemayı adımlara bölün, bütünü de saklayın. Her adımın kendi şeması olur; tam şema bunların birleşimidir. “İleri” düğmesi yalnız o adımın şemasını çalıştırır, son gönderimde ve sunucuda tam şema çalışır. Zod’da nesne şemaları
extendile (Zod 3’temergeile de) birleştirilir; Zod 4’temergekullanımdan kaldırılmış olarak işaretlendiği içinextendtercih edilmelidir. Adımlar arası kurallar tam şemaya eklenir;refineuygulanmış bir şema artık genişletilemediği için önce birleştirme, sonrarefinegelir. - Taslağın yerine karar verin. Kısa sihirbazlarda form durumu bileşen içinde yeter. Uzun süreçlerde veya sayfa yenilemesine dayanıklılık gerekiyorsa taslağı sunucuda “taslak” durumunda bir kayıt olarak saklayın; tarayıcı depolaması kişisel veri içeren formlar için uygun değildir. Form durumunu global store’a taşıma eğilimi ve alternatifleri React’ta durum yönetimi rehberinde tartışılıyor.
- Geri dönüşte veriyi koruyun. Kullanıcı önceki adıma dönünce girdiği değerler kaybolmamalı. Önceki adımda değişen bir seçim sonraki adımların kurallarını etkiliyorsa ilgili alanlar yeniden doğrulanır.
Bir adımda girilmiş bilgiyi sonraki adımda tekrar istemek kullanıcıyı yorar ve WCAG 2.2’nin 3.3.7 Redundant Entry ölçütüyle çelişir. Daha önce girilen değerleri otomatik doldurun veya seçilebilir yapın.
Sunucu hatalarını kullanıcıya göstermek
Sunucu, istemciden geçmiş bir değeri reddettiğinde hata kullanıcının düzeltebileceği biçimde ve doğru alanın yanında görünmelidir. Bunun için API ile ön yüz arasında öngörülebilir bir hata sözleşmesi şarttır. Sözleşme yoksa her ekran hatayı farklı yorumlar. Yukarıdaki örnekte 422 durum kodu ve alan adına göre gruplanmış mesajlar kullanıldı; HTTP API’leri için standart bir hata gövdesi isteniyorsa RFC 9457 (Problem Details for HTTP APIs) biçimini temel alıp alan hatalarını bir uzantı üyesi olarak ekleyebilirsiniz.
// İstemci: sunucu hatalarını React Hook Form alanlarına eşleme
const onSubmit = async (veri: SatinalmaTalebi) => {
const yanit = await fetch('/api/satinalma', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(veri),
});
if (yanit.status === 422) {
const { alanHatalari } = await yanit.json();
for (const [alan, mesajlar] of Object.entries(alanHatalari)) {
setError(alan as keyof SatinalmaTalebi, { type: 'server', message: mesajlar[0] });
}
return;
}
if (!yanit.ok) {
setError('root.server', { type: 'server', message: 'Kayıt yapılamadı, lütfen tekrar deneyin.' });
}
};
Hata gösteriminde kontrol edilecekler:
- Hata mesajı alana
aria-describedbyile bağlanır, alanaria-invalid="true"ile işaretlenir; yalnız kırmızı renk yetmez. - Gönderimde birden fazla hata varsa formun başına bir özet koyup odağı oraya veya ilk hatalı alana taşıyın.
- Mesaj neyin yanlış olduğunu ve nasıl düzeltileceğini söyler: “Geçersiz değer” yerine “Maliyet merkezi AA-1234 biçiminde olmalı”.
- Sunucunun iç hata ayrıntıları (SQL hatası, yığın izi) kullanıcıya iletilmez; bunlar sunucu loguna yazılır, kullanıcıya genel bir mesaj ve gerekirse izleme kimliği gösterilir. Destek ekibi o kimlikle logda ilgili kaydı bulur.
- Kullanıcı yazarken her tuş vuruşunda hata göstermeyin, alan odaktan çıkınca doğrulayın; hata bir kez gösterildikten sonra düzeltme sırasında anlık güncellemek kabul edilebilir.
Erişilebilir hata gösteriminin ayrıntıları için WCAG 2.2 geliştirici rehberindeki form bölümüne bakın.
Doğrulama kontrol listesi
- Her form için tek bir şema var; istemci ve sunucu onu ortak modülden içe aktarıyor.
- Sunucu istek gövdesini iş mantığından önce şemayla doğruluyor; doğrulanmamış veri servis katmanına geçmiyor.
- Şemada tanımlı olmayan alanlar yok sayılıyor ya da reddediliyor (toplu atama riskine karşı); rol, sahiplik, onay durumu gibi alanlar istemciden alınmıyor.
- Metin alanlarında azami uzunluk tanımlı; sayısal alanlarda alt ve üst sınır var.
- Kimlik ve referans alanlarının (departman, proje, müşteri) varlığı ve kullanıcının bunlara yetkisi sunucuda kontrol ediliyor.
- Tekillik ve ilişki kuralları veritabanı kısıtlarıyla da korunuyor; kısıt ihlali alana eşlenmiş mesaja çevriliyor.
- Asenkron kontroller debounce ile sınırlanmış ve gönderimde sunucuda tekrarlanıyor.
- API hata sözleşmesi belgelenmiş; alan hataları ile genel hatalar ayrı taşınıyor.
- Hata mesajları erişilebilir biçimde alanlara bağlı ve düzeltme yönü gösteriyor.
- Kişisel veri içeren alanlarda saklama ve aydınlatma yükümlülükleri ayrıca değerlendirilmiş; konu KVKK uyumlu iletişim formu rehberinde ele alınıyor.
Onay akışı, rol bazlı alanlar ve çok adımlı talepler içeren iş formları geliştiriyorsanız form, talep ve onay sistemleri kapsamında doğrulama katmanını da baştan tasarlıyoruz.
Sık sorulan sorular
İstemci doğrulaması varken sunucuda tekrar doğrulamak gerekir mi?
Evet. İstemci doğrulaması yalnız kullanıcı deneyimi içindir; tarayıcı araçlarıyla ya da doğrudan API isteğiyle kolayca atlatılır. Veri bütünlüğü ve güvenlik için bağlayıcı kontrol her zaman sunucuda yapılır.
Aynı şemayı istemci ve sunucuda kullanmanın riski var mı?
Şema modülü sunucuya özgü bağımlılık içermediği sürece risk yoktur. Veritabanı sorgusu gerektiren kurallar ortak şemaya değil, sunucudaki ek doğrulama adımına konur; aksi halde sunucu kodu istemci paketine sızabilir.
Doğrulama hatası için hangi HTTP durum kodu kullanılmalı?
Yaygın tercihler 400 Bad Request ve 422 Unprocessable Content’tir. Asıl mesele kodun API genelinde tutarlı olması ve yanıt gövdesinde alan bazlı hataların öngörülebilir bir yapıda dönmesidir.
Asenkron doğrulama her tuş vuruşunda çalışmalı mı?
Hayır. Sunucuya istek atan kontroller alan odaktan çıkınca ve kısa bir gecikmeyle çalışmalıdır. Sonuç yalnız bilgilendirme amaçlıdır; kesin kontrol gönderim anında sunucuda tekrarlanır.
- Teknoloji
- TypeScript, Zod, React Hook Form, Next.js Route Handlers, HTTP 422
- Konu
- Şema tabanlı istemci ve sunucu form doğrulaması
- Seviye
- Orta
- Ön koşullar
- TypeScript ve React temel bilgisi
- REST API ile form gönderimi deneyimi
Modern Web Uygulamaları