
Wed Sep 30 2026

Hadi başlayalım! 👋
TypeScript ile yazdığımız kodda derleme zamanında tip güvenliği harika çalışıyor. Ama gerçeğe bakalım: çalışma zamanında (runtime) JavaScript'e dönüşen kod, tipler hakkında hiçbir şey bilmiyor. TypeScript'in sağladığı tüm güvence, tsc derleyicisi bittiği anda ortadan kalkıyor.
İşte tam bu noktada Type Guard devreye giriyor. 🛡️
Basitçe: "Bu değer şu tipte mi?" sorusuna çalışma zamanında cevap veren, TypeScript'e de bu cevabı anlatan küçük fonksiyonlardır.
Neden kritik?
string dışında herhangi bir şey olabilirany döndürürType Guard yazmazsan, as string cast'larıyla "umarım doğru olur" diyorsun. Type Guard yazarsan, kanıtlama yapıyorsun. ✅
isString GuardEn basit haliyle bir isString guard fonksiyonu şöyle yazılır:
function isString(val: unknown): val is string {
return typeof val === 'string';
}
Ne oluyor burada?
| Parça | Anlamı |
|---|---|
val: unknown |
Gelen değerin tipini bilmiyoruz (güvenli başlangıç) |
val is string |
Type predicate — TypeScript'e "eğer true dönerse bu stringdir" diyoruz |
typeof val === 'string' |
Çalışma zamanındaki gerçek kontrol |
function process(input: unknown) {
if (isString(input)) {
// 🎉 TypeScript artık `input` tipini `string` biliyor!
console.log(input.toUpperCase()); // Hata yok, güvenli
} else {
console.log('Bu bir string değil:', input);
}
}
Bu sayede ne oluyor?
input daraltılmış (narrowed) tipte işleniyor → hata yakalanıyortypeof kontrolü yapılıyor → güvenli davranıyorHazırsan bir sonraki bölümde built-in guard'ları (typeof, instanceof, Array.isArray) ve custom guard pattern'larını tek tek inceleyelim. 🚀
Hazırsan bu sorunu bir senaryo üzerinden anlayalım 🎯
email alanı contact.email olarak taşındı).true dönüyor.undefined ile karşılaşıyorsunuz.// --- Eski tanım ---
interface User {
id: number;
name: string;
email: string; // 👈 eski yer
}
// --- Eski guard (hâlâ kullanılmakta) ---
function isUser(obj: unknown): obj is User {
return (
typeof obj === "object" &&
obj !== null &&
"id" in obj &&
"name" in obj &&
"email" in obj // guard hâlâ email'i doğrudan kontrol ediyor
);
}
// --- Yeni API yanıtı (backend değişti) ---
const apiResponse = {
id: 42,
name: "Ada",
contact: {
email: "ada@example.com" // 👈 email artık contact altında
}
};
// Guard çalışıyor, true dönüyor ❗️
console.log(isUser(apiResponse)); // true
// Ama gerçekte email yok ❌
console.log((apiResponse as User).email); // undefined 🚨
User) ile runtime verisi (apiResponse) birbirinden sapıyor.true dönse bile nesne yapısı artık uygun değil.Kısaca: Guard’larımız sadece şekil kontrol ediyor, içerik değişince fark etmiyor. Bu yüzden guard’ları da veri şemasıyla senkronize etmek (veya runtime şema doğrulaması eklemek) zorunlu hale geliyor 🛠.
Type Guard kavramını duydunuz, belki de kullanıyorsunuzdur. Ama arka planda ne olduğunu bilmek, hata ayıklarken (debug) size büyük avantaj sağlar 🎯
Kodunuz tsc (veya esbuild, swc vb.) ile derlendiğinde:
interface, type, as, generic parametreler yok olurisString guard'ıTypeScript kaynağı:
function isString(value: unknown): value is string {
return typeof value === "string";
}
function process(input: unknown) {
if (isString(input)) {
// Burada TypeScript input'u 'string' bilir
console.log(input.toUpperCase());
} else {
console.log("String değil:", input);
}
}
Derlenmiş JavaScript çıktısı:
function isString(value) {
return typeof value === "string";
}
function process(input) {
if (isString(input)) {
console.log(input.toUpperCase());
} else {
console.log("String değil:", input);
}
}
| TypeScript'ta | JavaScript'te |
|---|---|
value is string → type predicate |
Tamamen yok |
unknown tipi |
value parametresi (tip yok) |
| Derleme anında tip daraltma (narrowing) | Sıradan if kontrolü |
Guard fonksiyonu çalışma zamanında sadece typeof value === "string" döner. TypeScript'in bildiği "bu fonksiyon true döndürürse value string'tir" bilgisi derleme sonrası kaybolur 💨
Runtime'da garanti YOK. Şu senaryoyu düşünün:
const data = JSON.parse('{"name": 123}'); // name number geldi!
// Tip: { name: string } — ama runtime'da number!
Guard'ınız typeof value.name === "string" kontrol ederse false döner, kodunuz else koluna girer. Ama TypeScript derleme anında sizi uyarmaz çünkü tip sistemi zaten { name: string } diye biliyor ✅
typeof, instanceof, Array.isArray... kontrollerinizin kalitesine bağlıBu yüzden guard'larınızı sağlam yazın — TypeScript onları derlerken kullanır, ama çalışırken siz onları çalıştırırsınız 🎯
Hadi guard fonksiyonlarını gerçek verilerle test edelim ki API değişiminde (drift) erken uyarılım 🚨
Jest veya Vitest gibi bir çerçeve kullanıp testleri CI/CD pipeline'ına eklersek, her push'ta otomatik kontrol olur ✅
isUser guard testi 🛠Aşağıda Jest + TypeScript ile yazılmış bir test dosyası var. Hem geçerli hem de bozuk veri senaryolarını kapsıyor.
// guards/user.guard.ts
export interface User {
id: string;
name: string;
email: string;
}
export function isUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'name' in data &&
'email' in data &&
typeof (data as Record<string, unknown>).id === 'string' &&
typeof (data as Record<string, unknown>).name === 'string' &&
typeof (data as Record<string, unknown>).email === 'string'
);
}
// guards/user.guard.test.ts
import { isUser, User } from './user.guard';
describe('isUser guard fonksiyonu', () => {
const validUser: User = {
id: '123',
name: 'Ahmet',
email: 'ahmet@example.com',
};
test('✅ geçerli user objesi için true dönmeli', () => {
expect(isUser(validUser)).toBe(true);
});
test('❌ eksik alanlı obje için false dönmeli', () => {
const incomplete = { id: '123', name: 'Ahmet' }; // email yok
expect(isUser(incomplete)).toBe(false);
});
test('❌ yanlış tipte alanlı obje için false dönmeli', () => {
const wrongType = { id: 123, name: 'Ahmet', email: 'ahmet@example.com' }; // id number
expect(isUser(wrongType)).toBe(false);
});
test('❌ null/undefined için false dönmeli', () => {
expect(isUser(null)).toBe(false);
expect(isUser(undefined)).toBe(false);
});
test('❌ primitive değerler için false dönmeli', () => {
expect(isUser('string')).toBe(false);
expect(isUser(123)).toBe(false);
expect(isUser(true)).toBe(false);
});
});
npm test (veya vitest run) komutunu çalıştırır.email alanı eMail olur), testler kırılır ⛔Özetle: Guard fonksiyonlarınızı gerçek verilerle test edin, testleri pipeline'a ekleyin. Böylece tip drift'i erken yakalar, gece yarısı şaşkınlık yaşamazsınız 😄
Hazırsan Zod (veya Valibot) ile tanışalım.
Bu kütüphaneler şema tabanlı çalışır: veri yapısını bir kez tanımlarsın, hem runtime doğrulama hem de compile‑time tip çıkarımı alırsın 🎯
parse / safeParse: hata fırlatmak veya success boolean’ı ile kontrol etmek senin elinde 🔁z.infer<typeof UserSchema>: TypeScript tipi otomatik üretilir, el ile yazmana gerek kalmaz 🛠Şemadan doğrudan bir type guard üretirsin. Böylece if (isUser(data)) yazdığında TypeScript artık data’nın User tipinde olduğunu bilir ❗️
import { z } from "zod";
// 1️⃣ Şema tanımı
const UserSchema = z.object({
id: z.number().int().positive(),
name: z.string().min(1),
email: z.string().email(),
isActive: z.boolean().optional(),
});
// 2️⃣ TypeScript tipi çıkarımı
type User = z.infer<typeof UserSchema>;
// 3️⃣ Guard fonksiyonu – runtime kontrol + compile‑time daraltma
const isUser = (value: unknown): value is User =>
UserSchema.safeParse(value).success;
// Kullanım örneği
const rawData = { id: 42, name: "Ada", email: "ada@example.com" };
if (isUser(rawData)) {
// Artık rawData: User
console.log(`Hoş geldin, ${rawData.name}!`);
} else {
console.warn("Geçersiz kullanıcı verisi ❌");
}
Ne oluyor burada?
UserSchema ile veri şeklini bir kez tanımlıyorsun.z.infer sayesinde User tipi otomatik oluşuyor.isUser guard’ı safeParse sonucunu true/false olarak döndürür ve TypeScript’e value is User bildirir.Böylece hem çalışma anında güvenli doğrulama hem de derleme anında tam tip desteği elde edersin 🚀
Hazırsan branded (markalı) tipler konusuna girelim.
Bu desen, TypeScript’in structural typing doğasını biraz “nominal” hale getiriyor — yani iki string aynı görünse bile, farklı markalar taşıyorsa derleyici onları ayırt ediyor 🎯.
UserId ve ProductId aynı string olsa bile, yanlış atama derleme anında yakalanır ❗️type Brand<K, T> = K & { __brand: T };
K → asıl değer tipi (ör. string, number)T → benzersiz bir marka (string literal)& intersection ile __brand alanı eklenir, runtime’da hiçbir şey yok 🤫UserIdtype UserId = Brand<string, 'UserId'>;
type ProductId = Brand<string, 'ProductId'>;
function getUserById(id: UserId) { /* ... */ }
function getProductById(id: ProductId) { /* ... */ }
const uid = "abc-123" as UserId; // ✅ doğru marka
const pid = "xyz-789" as ProductId; // ✅ doğru marka
getUserById(uid); // ✅ derlenir
getUserById(pid); // ❌ HATA: ProductId → UserId atanamaz
Ne oluyor burada?
as UserId ile string’i markalı hale getiriyoruz.__brand alanını görüyor ve farklı markalar için atama reddediyor.uid ve pid yine düz string, ekstra kod yok 🚀Factory fonksiyon yazarsan
askastetmen gerekmez:
function createUserId(raw: string): UserId {
return raw as UserId; // tek nokta, merkezi doğrulama
}
Böylece tüm giriş noktalarında markalama merkezi olur, kod temiz kalır ✨.
Özet: Branded types, compile‑time’da yanlış kimlik atamalarını engellemek için çok hafif ve güçlü bir yoldur. Bir sonraki bölümde bunu validation ile birleştirip runtime’da da güvence altına alacağız 🔜.
Harika, şimdi öğrendiklerimizi birleştirip uçtan uca tip güvenli bir pipeline kuralım 🎯
Senaryo basit: dışarıdan gelen (API, form, CSV…) ham kullanıcı verisini alıp, doğrulayıp, branded type'lara çevirip, iş mantığımızda güvenle kullanalım.
Hazırsan dosya dosya gidelim 👇
user.types.ts — Branded Type TanımlarıÖnce domain'imizin dilini konuşan tipleri yazıyoruz.
brand yardımıyla primitive'leri anlamlı tiplere dönüştürüyoruz.
// user.types.ts
type Brand<T, B> = T & { __brand: B };
export type UserId = Brand<string, 'UserId'>;
export type Email = Brand<string, 'Email'>;
export type UserName = Brand<string, 'UserName'>;
export type Age = Brand<number, 'Age'>;
export interface User {
id: UserId;
email: Email;
name: UserName;
age: Age;
createdAt: Date;
}
/** Yardımcı: branded type oluşturucu */
export const createUserId = (v: string): UserId => v as UserId;
export const createEmail = (v: string): Email => v as Email;
export const createUserName = (v: string): UserName => v as UserName;
export const createAge = (v: number): Age => v as Age;
Ne kazandık? Artık
stringyerineUserNamepaslayamayız — TypeScript bize derleme anında uyarır ✅
user.schema.ts — Zod Şeması ve DönüşümŞimdi runtime doğrulama + branded type'a çevirme bir arada.
// user.schema.ts
import { z } from 'zod';
import type { User, UserId, Email, UserName, Age } from './user.types';
// 1️⃣ Ham veri şeması (API'den gelen JSON gibi)
export const RawUserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(2).max(50),
age: z.number().int().min(13).max(120),
createdAt: z.string().datetime(), // ISO string
});
// 2️⃣ Branded type'lı "temiz" şema — transform ile çeviriyoruz
export const UserSchema = RawUserSchema.transform((raw): User => ({
id: raw.id as UserId,
email: raw.email as Email,
name: raw.name as UserName,
age: raw.age as Age,
createdAt: new Date(raw.createdAt),
}));
// 3️⃣ Tip çıkarımı — artık `User` tipinde güvenli
export type ValidatedUser = z.infer<typeof UserSchema>; // User
Ne oluyor burada?
RawUserSchema → ham veriyi temizler (gereksiz alanları atar, format kontrolü yapar).transform() → doğrulama başarılıysa branded type'lı User objesini üretirz.infer sayesinde tip ve runtime tek kaynaktan gelir 🔁user.guard.ts — Guard Fonksiyonu (Type Narrowing)Kodun akışında unknown/any gelirse tip daraltmak için guard.
// user.guard.ts
import { UserSchema } from './user.schema';
import type { User } from './user.types';
/**
* Gelen veri `User` ise true döner ve TypeScript tipi `User` olarak daraltır
* Değilse false — hata yönetimi çağırana bırakılır
*/
export function isValidUser(data: unknown): data is User {
const result = UserSchema.safeParse(data);
return result.success;
}
/**
* Hata detayı da lazımsa — parse edip `Result` tuple döndüren versiyon
*/
export function parseUser(data: unknown):
| { success: true; data: User }
| { success: false; errors: z.ZodError } {
const result = UserSchema.safeParse(data);
if (result.success) return { success: true, data: result.data };
return { success: false, errors: result.error };
}
Neden guard?
if (isValidUser(payload)) { /* payload artık User */ }yazıp type narrowing kazanıyorsunuz.
parseUserise hata mesajlarını UI/log'a basmak için güzel 🛠
user.test.ts — Test Dosyası (Vitest/Jest)Pipeline'ın tüm kenar durumlarını test edelim.
// user.test.ts
import { describe, it, expect } from 'vitest';
import { UserSchema } from './user.schema';
import { isValidUser, parseUser } from './user.guard';
import type { User } from './user.types';
const validRaw = {
id: '550e8400-e29b-41d4-a716-446655440000',
email: 'ada@lovelace.dev',
name: 'Ada Lovelace',
age: 36,
createdAt: '2024-01-15T10:30:00.000Z',
};
describe('User Pipeline', () => {
it('geçerli veriyi User objesine çevirmeli', () => {
const result = UserSchema.safeParse(validRaw);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.email).toBe('ada@lovelace.dev');
expect(result.data.createdAt).toBeInstanceOf(Date);
}
});
it('geçersiz email reddedilmeli', () => {
const bad = { ...validRaw, email: 'not-an-email' };
const result = UserSchema.safeParse(bad);
expect(result.success).toBe(false);
});
it('yaş sınırları dışındaysa hata vermeli', () => {
const tooYoung = { ...validRaw, age: 10 };
const tooOld = { ...validRaw, age: 200 };
expect(UserSchema.safeParse(tooYoung).success).toBe(false);
expect(UserSchema.safeParse(tooOld).success).toBe(false);
});
it('guard fonksiyonu type narrowing sağlamalı', () => {
const unknownInput: unknown = validRaw;
if (isValidUser(unknownInput)) {
// TS artık `unknownInput` tipini `User` bilir
expect(unknownInput.name).toBe('Ada Lovelace');
} else {
throw new Error('Test verisi geçerli olmalı');
}
});
it('parseUser hata detayı döndürmeli', () => {
const result = parseUser({ ...validRaw, email: 'bad' });
expect(result.success).toBe(false);
if (!result.success) {
expect(result.errors.issues.length).toBeGreaterThan(0);
}
});
});
Çalıştır:
npm test user.test.ts
Tüm testler yeşil gelmeli ✅
Şimdi hepsini birleştirip gerçek bir fonksiyonda nasıl kullanılır:
// user.service.ts
import { parseUser } from './user.guard';
import type { User } from './user.types';
export function registerUser(raw: unknown): User {
const parsed = parseUser(raw);
if (!parsed.success) {
// Hata logla, custom error fırlat, vb.
throw new Error(`Geçersiz kullanıcı verisi: ${parsed.errors.message}`);
}
// parsed.data artık tam tip güvenli User
saveToDatabase(parsed.data);
sendWelcomeEmail(parsed.data.email);
return parsed.data;
}
function saveToDatabase(user: User) { /* ... */ }
function sendWelcomeEmail(email: string) { /* ... */ }
| Katman | Ne Sağlar? |
|---|---|
| Branded Types | Compile-time: Email ≠ UserName karışmaz |
| Zod Schema | Runtime: geçersiz veri hiç girmez |
| Transform | Ham veri → Domain objesi tek adımda |
| Guard | unknown → User type narrowing |
| Testler | Her kural kanıtlanmış |
Bu dosyaları projenize kopyalayın, npm i zod vitest kurun ve hemen deneyin 🚀
Sorunuz olursa yanımdayım — birlikte debug ederiz!
Hazırsan son bir tur atalım ve guard drift'i sürekli öne alalım 🚀
- name: Run tests
run: npm ci && npm test
.husky/pre-commit örnek:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run lint # ESLint/TSLint
npm run format # Prettier
npm test # Hızlı unit testleri
Not:
npm run lintvenpm run formatscriptlerinipackage.json’da tanımlamayı unutma.
no-unused-vars, prefer-const, guard-for-in gibi kuralları error seviyesinde tutrequire-guard-clause) ekle → kod standartları otomatik denetlenireslint.config.js (flat config) parçası:
export default [
{
rules: {
"guard-for-in": "error",
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"prefer-const": "error"
}
}
];
if (!condition) return; pattern’i arıyoruzSon söz: Bu alışkanlıkları ekibe yaydığında, kod bazında “neden bu if var?” sorusu yerini “harika bir guard clause!” beyanına bırakır 🎉
Hadi birlikte temiz, güvenli ve sürdürülebilir kod yazmaya devam edelim! 🚀
Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.
All rights reserved