Programlama

TypeScript Utility Type’ları: SaaS’ta Gerçekten İşe Yarayanlar

Şunu itiraf etmeliyim — TypeScript’in utility type’larını ilk öğrendiğimde aklımdan şu geçti: “Bunları gerçek projede ne zaman kullanacağım ki?” Cheatsheet’lerde hep karşına çıkıyor: Partial, Pick, Omit, Record (ki bu çoğu kişinin gözünden kaçıyor). Tamam, güzel. Ama pratikte? Yani gerçek bir SaaS projesi yazarken, Stripe entegrasyonu yaparken, Next.js API route’larında veri döndürürken — o anlarda bu şeyleri aklında tutabiliyor musun? İşte tam da burada, teoride kalan bu utility type’ların gerçek production kodunda nasıl hayat kurtardığını anlatacağım. Kendi deneyimlerimden, hatta dürüst olmak gerekirse hatalarımdan yola çıkarak.

2023 sonunda bir freelance SaaS projesi üzerinde çalışıyordum — subscription yönetimi olan, Stripe ile entegre bir dashboard. İlk başta tipleri elle yazmaya çalıştım, tabi. Sonra üçüncü kez aynı alanları kopyala-yapıştır yaptığımı fark edince duraksadım: “Dur bir dakika, bunun daha akıllı bir yolu olmalı.” Ve gerçekten vardı. Hem de tam orada, gözümün önünde (ciddiyim)

Çok konuştum, örnekle göstereyim.

Partial<T> ile Update İşlemleri: Az Yazıp Çok İş Çıkarmak

SaaS uygulamalarında en sık karşılaşılan senaryo ne biliyor musun? PATCH endpoint’leri (bizzat test ettim). Kullanıcı sadece planını değiştirmek istiyor, sadece ismini güncellemek istiyor ya da sadece e-posta tercihlerini. Tüm alanları zorunlu kılmak saçmalık. İşte tam burada Partial devreye giriyor.

type User = {
id: string;
email: string;
name: string;
plan: 'free' | 'pro';
stripeCustomerId: string;
};
// PATCH endpoint — sadece değişen alanları gönder
async function updateUser(
id: string,
updates: Partial<Omit<User, 'id'>>
) {
return db.update(users).set(updates).where(eq(users.id, id));
}
// Kullanım: sadece plan ve Stripe ID değişti
await updateUser(userId, {
plan: 'pro',
stripeCustomerId: 'cus_abc123'
});

Bakın burada ince bir numara var: Partial<Omit<User, 'id'>> kombinasyonu. Neden? Çünkü birisi gelip update payload’ında id göndermesin istiyorsun. Bu, 2024’te çalıştığım bir projede tam olarak başıma geldi — bir junior developer update fonksiyonuna id alanını da geçirmişti, veritabanında yanlış kayıt güncelleniyordu, kim fark etti dersin, saatlerce sürdü o debug. Partial + Omit birleşimi bu hatayı derleme zamanında yakalıyor. Tabi runtime’da da doğrulama yapman lazım ama tip sistemi ilk savunma hattın oluyor, bunu küçümseme.

Küçük bir startup’ta çalışıyorsan belki “abartıyorsun, 3 alan var zaten” diyebilirsin. Haklısın. O ölçekte belki elle de halledersin, itiraz etmiyorum. Ama 15-20 alanlı bir kullanıcı modelin olduğunda — ki enterprise SaaS’larda bu çok normal, hatta az bile sayılır — Partial olmadan yaşayamazsın. Gerçekten.

Durun, bir saniye.

Pick ve Omit: Frontend’e Ne Kadar Veri Sızdırıyorsun?

Pick — Küçük ve Güvenli Alt Kümeler

Geçen yıl bir güvenlik denetiminde şunu öğrendim: API response’larında gereksiz alan döndürmek, aktif bir güvenlik açığı olmasa bile “information disclosure” kategorisinde değerlendiriliyor (şaşırtıcı ama gerçek). Ciddi mesele bu. Yani veritabanı modelini olduğu gibi frontend’e dönmek… kötü fikir.

type DbUser = {
id: string;
email: string;
passwordHash: string;
stripeCustomerId: string;
plan: 'free' | 'pro';
createdAt: Date;
};
// Tarayıcıya göndermesi güvenli olan kısım
type PublicUser = Pick<DbUser, 'id' | 'email' | 'plan'>;

Pick kullanınca şunu garanti ediyorsun: “Bu tipte sadece bu 3 alan var, başka bir şey sızmıyor.” Runtime’da da tabi bir filtreleme yapman gerekiyor, ama en azından tip sistemi seni uyarıyor. Bir arkadaşım geçen sene Stripe müşteri ID’sini yanlışlıkla frontend’e döndürmüş — zararlı bir şey olmadı ama code review’da yakalandığında bayağı utandı, hatırlarsanız diye de sordu bana sonra. Pick kullanmış olsa derleme zamanında hata alırdı. Mesele bu kadar basit.

Omit — Büyük Tiplerde Daha Pratik

Şimdi gelelim Omit’e. Pick’in tersine çalışıyor: “Şu alanlar hariç hepsini al.” Ne zaman hangisini kullanmalısın?

Durum Kullan Neden
Güvenli alan sayısı az (2-4) Pick Neyin dışarı gittiğini açıkça belirtiyorsun
Tehlikeli alan sayısı az (1-2) Omit Çoğu alanı zaten göndermek istiyorsun
Model sık değişiyor Pick (daha güvenli) Yeni eklenen hassas alan otomatik dışarı sızmaz
Dahili servisler arası iletişim Omit Güvenlik riski daha düşük, pratiklik öne çıkıyor

Pratik kural: Eğer modeline yeni bir hassas alan eklendiğinde otomatik olarak frontend’e sızmasını istemiyorsan, Pick tercih et. Omit kullanırken yeni eklenen her alanın “varsayılan olarak dışarı açık” olduğunu unutma.

Bu ayrım küçük görünüyor. Ama inan bana, 20+ alanlı bir veritabanı modelinde hayat kurtarıcı — lafı gevelemeden söyleyeyim. Bir keresinde Omit kullanıyordum, sonra modele internalNotes diye bir alan eklendi — müşteri destek notları, gayet hassas bilgiler. Omit listesine eklemeyi unuttum ve bu notlar birkaç saat boyunca API’dan döndü. Neyse ki staging’de yakaladık ama… dersi aldım. Acıyla, ama aldım.

Record<K, V>: Tipli Sözlükler ve Feature Flag Yönetimi

Record tipi belki en “sıkıcı” görüneni. Ama SaaS’ta en çok işe yarayanlardan biri, şaşırdım açıkçası bunu fark edince. Mesela feature flag’ler, plan bazlı ayarlar ve environment ayarları için biçilmiş kaftan. Bu konuyla ilgili Raspberry Pi OLED Ekranları İçin Python Çatısı: Temiz Yol yazımıza da göz atmanızı tavsiye ederim.

// Plan bazlı özellik listesi
const planFeatures: Record<'free' | 'pro' | 'enterprise', string[]> = {
free: ['5 API çağrısı/gün', '1 workspace'],
pro: ['sınırsız API', '10 workspace', 'öncelikli destek'],
enterprise: ['sınırsız her şey', 'SLA', 'SSO'],
};
// Ortam bazlı model ayaru
const modelConfig: Record<'development' | 'staging' | 'production', string> = {
development: 'claude-haiku-4-5-20251001',
staging: 'claude-sonnet-4-6',
production: 'claude-opus-4-6',
};

Record’un asıl gücü şurada: yarın gelip plan tipine 'team' diye yeni bir variant ekle bakalım — TypeScript derleyicisi hemen planFeatures objesinde team anahtarının eksik olduğunu söyleyecek. Bu, runtime’da “undefined” hatası almak yerine derleme zamanında yakalamak demek. Baya güzel, değil mi? Runtime sürprizleri pek sevilmez zaten.

Ha, bir de şunu söyleyeyim — Record’u LLM model konfigürasyonlarında çok kullanıyorum son zamanlarda. AI’yi Projene 2 Saatte Eklemenin Gerçek Hali yazımızda da bahsettiğimiz gibi, farklı ortamlarda farklı modeller kullanmak oldukça yaygın bir pattern. Record tipi bu mapping’i hem type-safe hem de okunabilir hale getiriyor. İkisi birden. Nadir rastlanır.

İşte tam da bu noktada devreye giriyor.

ReturnType<T>: Fonksiyon Dönüş Tiplerini Elle Yazmayı Bırak

Bu utility type’ı keşfettiğim an — abartmıyorum — “neden bunu daha önce kullanmıyordum?” diye kendi kendime söylendim. Mesela Drizzle ORM ya da Prisma gibi araçlarla çalışırken, fonksiyonların döndürdüğü tipleri elle tanımlamak bazen imkansıza yakın bir hal alıyor; ilişkiler, join’ler, select’ler derken tip tanımı 50 satırı geçiyor, okunmaz hale geliyor, kim yazdıysa lanetlenesi bir şey oluyor ortaya çıkan.

async function getSubscriptionWithUser(subscriptionId: string) {
return db.query.subscriptions.findFirst({
where: eq(subscriptions.id, subscriptionId),
with: { user: true, plan: true },
});
}
// Bu fonksiyonun dönüş tipini elle yazmak yerine:
type SubscriptionWithUser = NonNullable
Awaited<ReturnType<typeofgetSubscriptionWithUser>>
>;

Burada üç utility type iç içe kullanılıyor: ReturnType fonksiyonun dönüş tipini çıkarıyor, Awaited async Promise’i çözüyor, NonNullable ise null/undefined ihtimalini kaldırıyor. Karmaşık mı görünüyor? Evet, biraz öyle. Ama alternatifi düşün — ORM’nin döndürdüğü o karmaşık ilişkisel tipi elle yazmak. Hmm… hayır, teşekkürler. Bu konuyla ilgili iPad Air 11 (M4): Fiyatına Göre İyi mi, Fazla mı İddialı? yazımıza da göz atmanızı tavsiye ederim.

💡 İpucu: ReturnType özellikle veritabanı katmanında, API response tiplerinde ve middleware fonksiyonlarında çok işe yarıyor. Fonksiyon imzasını değiştirdiğinde, o fonksiyona bağımlı tüm tipler otomatik güncelleniyor. Tek kaynak noktası (single source of truth) prensibi.

Discriminated Union + Extract: Event ve Webhook Yönetimi

Bir de şunu söyleyeyim — SaaS’ta Stripe webhook’larıyla uğraşan herkes bilir bu durumu: gelen event’in tipine göre farklı handler’lar çalıştırman gerekiyor. Discriminated union pattern’i tam burada hayat kurtarıyor. Daha fazla bilgi için Python’la Günlük İşleri Otomatikleştirin: Evde, İşte, Hayatta yazımıza bakabilirsiniz.

type WebhookEvent =
| { type: 'checkout.session.completed'; data: { sessionId: string; customerId: string } }
| { type: 'invoice.payment_failed'; data: { invoiceId: string; attemptCount: number } }
| { type: 'customer.subscription.deleted'; data: { subscriptionId: string } };
// Sadece belirli bir event tipini çekmek istiyorsan:
type PaymentFailedEvent = Extract<WebhookEvent, { type: 'invoice.payment_failed' }>;
function handlePaymentFailed(event: PaymentFailedEvent) {
console.log(`Ödeme başarısız, deneme: ${event.data.attemptCount}`);
// Kullanıcıya e-posta gönder, retry logic çalıştır vs.
}

Extract burada zarif bir iş yapıyor — union tipinden sadece istediğin varyantı çekip çıkarıyor. Bunu elle yapmaya çalışsan her event tipi için ayrı bir interface tanımlaman gerekir, sonra bunları bir union’da birleştirmen gerekir, sonra bakımı gerekir, sonra birisi gelip değiştirir, sonra… Neyse. Extract ile tek satırda halloluyor.

2024 başında bir Stripe entegrasyonu yazarken tam 12 farklı webhook event tipi yönetmem gerekti. On iki. Extract olmadan her birinin tipini ayrı ayrı tanımlamam gerekirdi. Extract ile sadece ana union’ı yazdım, gerisini TypeScript halletti. Express.js Güvenlik Testinde Dört Araç Birbirini Nasıl Doğruladı? yazımızda da benzer pattern’leri güvenlik bağlamında ele almıştık.

Pratikte Dikkat Edilmesi Gerekenler

Aşırı Soyutlama Tuzağı

Açık konuşayım — bu utility type’ları öğrenince her yerde kullanmak istiyorsun. Normal. Ama bazen basit bir interface tanımı yeterli. Partial<Pick<Omit<Extract<...>>>> gibi dört katmanlı tip zincirleri yazmaya başladıysan, dur bir saniye — okunabilirlik kayboluyor olabilir, bak bir daha.

Aslında, Kural şu: eğer bir meslektaşın tip tanımını okuyup 10 saniyede anlayamıyorsa, muhtemelen parçalaman gerekiyor. Ara tipler tanımla, anlamlı isimler ver. type SafeUserForDashboard = Pick<DbUser, 'id' | 'email' | 'plan'> gibi açıklayıcı isimler kullan. Hani ne farkı var diyorsunuz, değil mi? Kodun okunması da bir beceri, sakın küçümseme.

Runtime Güvenliğini Unutma

TypeScript’in tip sistemi derleme zamanında çalışıyor — runtime’da her şey düz JavaScript. Yani Pick ile “bu 3 alanı dön” desen bile, eğer runtime’da objeyi filtrelemezsen tüm alanlar yine dönecek (kendi tecrübem). Tip sistemi bir güvenlik ağı ama tek başına yeterli değil, bu çok önemli. Zod, io-ts gibi runtime validation kütüphaneleriyle birlikte kullanman şart.

Az önce “tip sistemi yeterli” gibi bir izlenim verdim, ama aslında tam da bu noktada dikkatli olmak lazım. Tip sistemi “bu olması gereken” diyor, runtime validation ise “bu gerçekten olan” diyor (yanlış duymadınız). İkisi birlikte çalışmalı. Biri olmadan diğeri eksik kalıyor.

Sıkça Sorulan Sorular

Partial ve Required arasındaki fark nedir?

Partial<T> tüm alanları opsiyonel yapar, Required<T> ise tam tersi — tüm alanları zorunlu kılar. Eğer bir tipin bazı alanları zaten opsiyonelse ve bunları zorunlu hale getirmek istiyorsan Required kullanırsın. Pratikte Partial çok daha sık kullanılıyor çünkü update/patch senaryoları daha yaygın.

Pick mı Omit mi kullanmalıyım? Hangisi daha güvenli?

Güvenlik açısından Pick genellikle daha güvenlidir çünkü “whitelist” mantığıyla çalışır — sadece belirttiğin alanlar geçer. Omit ise “blacklist” mantığıyla çalışır ve modele yeni eklenen alanlar otomatik dahil olur. Frontend’e veri dönerken Pick, dahili servisler arası iletişimde Omit tercih edilebilir.

ReturnType async fonksiyonlarda nasıl çalışır?

Async fonksiyonlar Promise döndürdüğü için ReturnType sana Promise<T> tipini verir (evet, doğru duydunuz). Promise’in içindeki asıl tipi almak için Awaited<ReturnType<typeoffn>> şeklinde Awaited ile sarmalaman gerekiyor. TypeScript 4.5 ile gelen Awaited tipi bu işi bayağı kolaylaştırdı.

Bu utility type’lar JavaScript’e derleniyor mu?

Küçük bir detay: Hayır. TypeScript utility type’ları büyük ölçüde derleme zamanında çalışır — kendi adıma konuşayım — ve JavaScript çıktısında hiçbir iz bırakmaz. Yani performans maliyeti sıfır — sadece geliştirici deneyimini iyileştiriyorlar. Runtime’da ek kontrol istiyorsan Zod gibi bir kütüphane kullanman gerekiyor.

Record tipi ile Map arasındaki fark nedir?

Record bir tip tanımıdır ve düz JavaScript objelerine derlenir (yanlış duymadınız). Map ise runtime’da çalışan bir veri yapısıdır — iterasyon sırası garantisi verir, herhangi bir tipi anahtar olarak kullanabilir. Eğer anahtarların string literal union ise Record, dinamik anahtarlarla çalışıyorsan Map daha uygun olur.

Kaynaklar ve İleri Okuma

TypeScript Handbook — Utility Types (Resmi Dokümantasyon)

İnanın, Type Challenges — TypeScript Tip Egzersizleri (GitHub)

TypeScript Utility Types That Actually Save Time in Production SaaS Code — Atlas Whoff (Orijinal Kaynak)

Aşkın KILIÇ

20+ yıl deneyimli Azure Solutions Architect. Microsoft sertifikalı bulut mimari ve DevOps danışmanı. Azure, yapay zekâ ve bulut teknolojileri üzerine Türkçe teknik içerikler üretiyor.

AZ-305AZ-104AZ-500AZ-400DP-203AI-102

Bu içerik işinize yaradı mı?

Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.

Haftalık Bülten

Her pazar özenle seçilmiş teknoloji yazıları doğrudan e-postanıza gelsin.

← Onceki Yazi
iPad Air 11 (M4): Fiyatına Göre İyi mi, Fazla mı İddialı?
Sonraki Yazi →
İlk Satırdan Fazlası: Küçük Bir Selamın Büyük Hikâyesi

Yorum Yaz

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Haftalık Bülten

Azure, DevOps ve Yapay Zeka dünyasındaki en güncel içerikleri her hafta doğrudan e-postanıza alın.

Spam yok. İstediğiniz zaman iptal edebilirsiniz.
📱
Uygulamayı Yükle Ana ekrana ekle, çevrimdışı oku
Kategoriler
Ara
Paylaş
İçindekiler
← iPad Air 11 (M4): Fiyatına Gör...
İlk Satırdan Fazlası: Küçük Bi... →
📩

Gitmeden önce!

Her pazar özenle seçilmiş teknoloji yazıları ve AI haberleri doğrudan e-postanıza gelsin. Ücretsiz, spam yok.

🔒 Bilgileriniz güvende. İstediğiniz zaman ayrılabilirsiniz.

📬 Haftalık bülten: Teknoloji + AI haberleri