Exploring Infinite Possibilities on FrontEnd 🚀
Burak Sağlık
Search...

Wed Sep 30 2026

Type Guard nedir? TypeScript'te Derleme Tip Güvenliği

Type Guard nedir? TypeScript'te Derleme Tip Güvenliği

🎯 Giriş: Type Guard Neden Önemli?

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. 🛡️

Ne İşe Yarar?

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?

  • 🔌 API yanıtları her zaman beklendiği gibi gelmez
  • 📥 Kullanıcı girdileri (formlar, query params) string dışında herhangi bir şey olabilir
  • 📦 Third-party kütüphaneler bazen any döndürür
  • 🧪 Testlerde ve mock verilerde tip güvenliği kaybolabilir

Type Guard yazmazsan, as string cast'larıyla "umarım doğru olur" diyorsun. Type Guard yazarsan, kanıtlama yapıyorsun. ✅


Pratik Bir Örnek: isString Guard

En 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

Nasıl Kullanılır?

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?

  • Derleme zamanında: input daraltılmış (narrowed) tipte işleniyor → hata yakalanıyor
  • Çalışma zamanında: Gerçek typeof kontrolü yapılıyor → güvenli davranıyor

Hazırsan bir sonraki bölümde built-in guard'ları (typeof, instanceof, Array.isArray) ve custom guard pattern'larını tek tek inceleyelim. 🚀


❗️ Sorun: Type Guard'lar Runtime'da Sessizce Eski Kalıyor

Hazırsan bu sorunu bir senaryo üzerinden anlayalım 🎯

Ne oluyor burada?

  • Backend ekibi API yanıtını güncelledi (örneğin email alanı contact.email olarak taşındı).
  • Front‑end tarafındaki type guard hâlâ eski yapıya göre true dönüyor.
  • Kod derleme zamanında hata vermiyor, ama runtime’da beklenmeyen undefined ile karşılaşıyorsunuz.
  • Hata mesajı yok — sadece sonraki bir yerde “cannot read property of undefined” patlaması oluyor 😱

Basit bir örnek

// --- 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 🚨

Bu sayede ne oluyor?

  • Type drift (tip kayması): Derleme tipi (User) ile runtime verisi (apiResponse) birbirinden sapıyor.
  • Guard yalan söylüyor — true dönse bile nesne yapısı artık uygun değil.
  • Hata sessizce bir sonraki kullanım anında çıkıyor, debug etmek zordur.

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 🛠.


🔁 Nasıl Çalışır: Type Guard Mantığı ve Tip Sistemi Bağımsızlığı

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 🎯


❗️ Temel gerçeği şu: TypeScript'in tip sistemi sadece derleme anında yaşar

Kodunuz tsc (veya esbuild, swc vb.) ile derlendiğinde:

  • Tüm interface, type, as, generic parametreler yok olur
  • Kalan sadece JavaScript'tir — tarayıcı veya Node.js bunu çalıştırır
  • Type Guard fonksiyonunuz da sıradan bir fonksiyona indirgenir

🛠 Örneğimiz: Basit bir isString 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);
  }
}

🔍 Ne oldu burada?

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 💨


⛔ Bu neden önemli?

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 ✅


✅ Özetle

  • Type Guard = Derleme anında tip daraltma aracı
  • JavaScript çıkışında = Sıradan boolean döndüren fonksiyon
  • Runtime güvenliği = Sizin 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 🎯


🛠 Çözüm 1: Otomatik Testlerle Drift Yakalama

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 ✅

Neden guard testleri? 🎯

  • Tip güvenliği derleme anında yetmez; runtime verisi farklı gelebilir.
  • Bozuk veri geldiginde hata fırlatması veya güvenli fallback bekleriz.
  • Testler canlı dokümantasyon görevi görür 📖

Pratik örnek: 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);
  });
});

Bu testler CI/CD'de nasıl çalışır? 🔁

  1. Pipeline her push/merge'te npm test (veya vitest run) komutunu çalıştırır.
  2. Eğer API'den dönen veri yapısı değişirse (örnek: email alanı eMail olur), testler kırılır ⛔
  3. Geliştirici anında drift'i görür, prod'a hatalı kod gitmez.

Ö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 😄


🛠 Çözüm 2: Şema Tabanlı Doğrulama (Zod / Valibot) ile Tür Güvenliği

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 🎯

Neden Zod / Valibot?

  • Tek kaynak: şema hem doğrulama hem tip için kullanılır → DRY prensibi ✅
  • 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 🛠

Guard fonksiyonu nasıl oluşur?

Ş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?

  1. UserSchema ile veri şeklini bir kez tanımlıyorsun.
  2. z.infer sayesinde User tipi otomatik oluşuyor.
  3. 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 🚀


🛠 Çözüm 3: Branded Types ile Compile-Time Koruma

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 🎯.

Neden Branded Type?

  • Tip güvenliği: UserId ve ProductId aynı string olsa bile, yanlış atama derleme anında yakalanır ❗️
  • Kod okunurluğu: İsimlendirmeyle amaç bellidir, ekstra runtime kontrolü gerekmez ✅
  • Sıfır maliyet: Sadece tip seviyesinde çalışır, JS çıktısı boş 🛠

Temel Desen

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 🤫

Pratik Örnek: UserId

type 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.
  • Derleyici __brand alanını görüyor ve farklı markalar için atama reddediyor.
  • Runtime’da uid ve pid yine düz string, ekstra kod yok 🚀

Küçük İpucu

Factory fonksiyon yazarsan as kastetmen 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 🔜.


🔁 Pratik Örnek: Kullanıcı Verisi İşleme Pipeline'ı

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 string yerine Email geçen bir fonksiyona yanlışlıkla UserName paslayamayı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 üretir
  • z.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.
parseUser ise 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 ✅


📦 Kullanım Örneği — Pipeline Akışı

Ş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) { /* ... */ }

🎯 Özet: Ne Kazandık?

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!


🎁 Bonus Tavsiye: Sürekli Entegrasyon ve Kod İncelemesi İpuçları

Hazırsan son bir tur atalım ve guard drift'i sürekli öne alalım 🚀

1. CI/CD'ye test entegrasyonu 🎯

  • Her push/pull‑request'te unit + integration test koşulsuz çalışmalı
  • Başarısız test → pipeline fail olsun, merge engellensin ⛔
  • Örnek GitHub Actions adımı:
- name: Run tests
  run: npm ci && npm test

2. Husky pre‑commit hook'ları 🔁

  • Commit öncesi lint + format + test otomatik tetiklesin
  • Geliştirici "unuttu" diye excuse yapamaz ✅

.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 lint ve npm run format scriptlerini package.json’da tanımlamayı unutma.

3. ESLint / TSLint kuralları 🛠

  • no-unused-vars, prefer-const, guard-for-in gibi kuralları error seviyesinde tut
  • Proje‑özel guard‑drift kuralları (ör. require-guard-clause) ekle → kod standartları otomatik denetlenir

eslint.config.js (flat config) parçası:

export default [
  {
    rules: {
      "guard-for-in": "error",
      "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
      "prefer-const": "error"
    }
  }
];

4. Kod incelemesinde (code review) dikkat edilecekler 👀

  • Guard clause var mı? → if (!condition) return; pattern’i arıyoruz
  • Early return kullanılmış mı? → Nested block’lardan kaçınılmış mı?
  • Test coverage değişimi var mı? → Yeni guard için en az bir test eklenmiş mi?
  • Lint uyarıları sıfır mı? → CI yeşil yanmamışsa merge etme ❗️

5. Küçük bir alışkanlık büyük fark yaratır ✨

  • Her PR’da “Guard drift kontrolü” checklist maddesi ekle
  • Takım arkadaşlarınla pair‑review yaparken bu maddeleri hızlıca tarayın

Son 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.

Burak Sağlık

Burak Saglik

©2024 Desing and Developed by @Burak Sağlık

All rights reserved