Paylaştığınız dosya, sıfırdan ileri seviyeye kadar giden detaylı bir TypeScript Eğitim Müfredatıdır. Bu uzun dosyayı tam da istediğiniz gibi hiç acele etmeden, adım adım, parça parça ve en ince detayına kadar ele alacağız. Dakika, saniye, ders sayısı ve önizleme gibi gereksiz kalabalıkları tamamen temizleyerek sadece saf bilgiye, açıklamalara ve kod örneklerine odaklanacağız.

İlk olarak en başından, yani "Öğrenecekleriniz" kısmından başlıyoruz.


BÖLÜM 1: "Öğrenecekleriniz" Paragrafının Açıklaması

Bu eğitim size TypeScript dünyasının kapılarını açmayı vadeder. Temel hedefi, sizi sadece teorik bilgiyle bırakmayıp, gerçek projeler üretebilecek bir donanıma ulaştırmaktır.

Müfredatın bu ilk giriş kısmı şu beş temel sözü verir:

  1. GitHub Üzerinde Hazır Kod Deposu: Her bölüm için ayrı ayrı hazırlanmış, bilgisayarınıza indirip hemen çalıştırabileceğiniz ZIP dosyaları ve kaynak kodlar sunulur. Böylece kod yazarken takıldığınızda hazır çalışan kodla kendi kodunuzu karşılaştırabilirsiniz.

  2. Derinlemesine İleri Seviye TypeScript Teorisi: Dilin sadece en basit kurallarını değil; büyük projelerde hayat kurtaran en gelişmiş ve karmaşık özelliklerini de en ince ayrıntısına kadar öğrenirsiniz.

  3. Hem Ön Yüz (Frontend) Hem Arka Yüz (Backend) Geliştirme: TypeScript'in sadece bir alana ait olmadığını görürsünüz. Web sitelerinin hem tarayıcıda çalışan görsel kısmında hem de sunucuda çalışan arka plan kısmında gerçek projeler inşa edersiniz.

  4. Modern Programlama Standartları: Nesne Yönelimli Programlama (OOP), Generic yapılar ve Decorator'lar gibi modern yazılım dünyasının en güçlü silahlarını TypeScript ile nasıl kullanacağınızı öğrenirsiniz.

  5. Güvenli ve Gerçekçi Bir API Projesi: Kursun sonunda popüler arka yüz çatısı olan Express ve veri tabanı yönetim aracı olan TypeORM'u kullanarak, dışarıdan gelebilecek tehlikelere karşı korunaklı, çalışan bir Node REST API (sunucu sistemi) ayağa kaldırırsınız.


BÖLÜM 2: TypeScript Nedir ve Neden Kullanılır?

Dosyanın ilk ders konularını kapsayan bu bölümde, TypeScript'in temellerine ve neden JavaScript yerine tercih edildiğine odaklanıyoruz.

1. Neden TypeScript? Temel Faydaları Neledir?

JavaScript esnek bir dildir fakat bu esneklik büyük projelerde ciddi hatalara yol açar. JavaScript'te bir değişkene önce bir sayı, sonra bir metin atayabilirsiniz. Yazılım büyüdükçe kimin neyi nereye gönderdiği belli olmaz ve program çöker.

TypeScript ise JavaScript'in üzerine kurulu bir "Süper Küme" (Superset) dildir. JavaScript'in yaptığı her şeyi yapar ama ona "Tip Güvenliği" (Type Safety) ekler.

2. TypeScript Statik Tip Sistemi vs JavaScript Dinamik Tip Sistemi

🎨 Uygulama ve Örnek

JavaScript ve TypeScript arasındaki farkı net bir şekilde görelim.

JavaScript Dünyası (Hata koruması yok):

let kullanıcıKodu = 12345; // Başta bir sayı
kullanıcıKodu = "Hakan";   // Sonradan metne dönüştü, JavaScript buna kızmaz.

function sepetiHesapla(fiyat, adet) {
    return fiyat * adet;
}
// Yanlışlıkla metin gönderdik!
console.log(sepetiHesapla("yirmi", 5)); // Çıktı: NaN (Not a Number) yani hata! Program çökmedi ama yanlış çalıştı.

TypeScript Dünyası (Tam koruma):

let kullanıcıKodu: number = 12345;
// kullanıcıKodu = "Hakan"; // TS DERLEYİCİSİ BURADA HATA VERİR: "string tipi number tipine atanamaz!"

function sepetiHesapla(fiyat: number, adet: number): number {
    return fiyat * adet;
}
// console.log(sepetiHesapla("yirmi", 5)); // TS Editörü hemen kırmızı çizer: "yirmi" bir sayı değildir!

3. TypeScript Derleyicisi (tsc) ve noEmitOnError Bayrağı

Tarayıcılar (Chrome, Safari, Edge) TypeScript kodunu doğrudan okuyamaz. Tarayıcıların anladığı tek dil JavaScript'tir. Bu yüzden yazdığımız .ts uzantılı dosyaları, TypeScript derleyicisi (tsc) yardımıyla düz .js dosyalarına dönüştürürüz. Bu işleme Transpilation (koddan koda çevirme) denir.


BÖLÜM 3: TypeScript Tip Sistemine Derin Dalış (Temel Tipler)

Bu kısımda dilin en temel yapı taşları olan ilkel (primitive) veri tiplerini inceliyoruz.

1. const, let ve var Farkları

🎨 Uygulama ve Örnek

if (true) {
    var sızanDeğişken = "Ben her yere giderim";
    let gizliDeğişken = "Ben sadece bu if içindeyim";
    const sabitDeğişken = "Beni de değiştiremezsin";
}
console.log(sızanDeğişken); // Çalışır! Çünkü var blok dinlemez.
// console.log(gizliDeğişken); // Hata verir! let dışarı sızamaz.

2. İlkel Tipler: number, string, boolean

TypeScript'teki en temel üç veri tipidir.

🎨 Uygulama ve Örnek

let yaş: number = 25;
let boy: number = 1.78;

let isim: string = "Ahmet";

let öğrenciMi: boolean = true;

3. Şablon Metinler (Template Strings)

Düz metinlerin içine dinamik olarak değişken yerleştirmemizi sağlar. Ters tırnak (backtick) ` işareti ile yazılır ve içerisine ${değişken} yapısı kurulur.

🎨 Uygulama ve Örnek

let ürünAdı: string = "Kablosuz Kulaklık";
let fiyat: number = 1500;

// Eski yöntem:
let mesajEski = "Aldığınız " + ürünAdı + " ürünü " + fiyat + " TL'dir.";

// Şablon Metin Yöntemi (Temiz ve Okunabilir):
let mesajYeni: string = `Aldığınız ${ürünAdı} ürünü tam ${fiyat} TL'dir.`;
console.log(mesajYeni);

4. Tip Çıkarımı (Type Inference) vs Tip Belirtimi (Type Annotation)

🎨 Uygulama ve Örnek

// Tip Belirtimi (Biz söyledik)
let şehir: string = "Ankara";

// Tip Çıkarımı (TS kendisi anladı)
let postaKodu = 06000; // TS bunu otomatik olarak 'number' yaptı.
// postaKodu = "İstanbul"; // Hata! TS der ki: "Sen belirtmedin ama ben onun sayı olduğunu biliyorum!"



Harika! Kaldığımız yerden, TypeScript'in en kritik ve günlük hayatta en çok kullanılan veri yapılarından devam ediyoruz. Dosyadaki gereksiz dakika, saniye ve ders sayılarını yine temizleyerek tamamen açıklamalara, mantığa ve kod örneklerine odaklanıyoruz.


BÖLÜM 4: Gelişmiş İlkel Tipler, Nesneler ve Koleksiyonlar

Bu bölümde TypeScript'in nesneleri, dizileri ve özel veri türlerini nasıl kontrol altına aldığını inceleyeceğiz.

1. Nesne Tipleri (Objects) ve İçiçe Geçmiş Nesneler (Nested Objects)

JavaScript'te nesneler (objeler) anahtar-değer çiftlerinden oluşur. TypeScript, bir nesnenin içinde hangi anahtarların olacağını ve bu anahtarların hangi veri tipine sahip olması gerektiğini tek tek belirlememizi zorunlu kılar. Eğer iç içe geçmiş bir yapı varsa, onları da derinlemesine tanımlayabiliriz.

🎨 Uygulama ve Örnek

// Basit ve İçiçe Geçmiş Nesne Tanımlaması
let kullanıcı: {
    id: number;
    isim: string;
    adres: {
        şehir: string;
        postaKodu: number;
    }
};

// Doğru Kullanım
kullanıcı = {
    id: 1,
    isim: "Can",
    adres: {
        şehir: "İzmir",
        postaKodu: 35000
    }
};

// kullanıcı.id = "iki"; // HATA! Sayı yerine metin girilemez.

2. Diziler (Arrays)

Bir dizi içerisinde sadece aynı türden verilerin (örneğin sadece sayılar veya sadece metinler) bulunmasını sağlamak için kullanılır.

🎨 Uygulama ve Örnek

let sayılar: number[] =;
let isimler: string[] = ["Ahmet", "Mehmet", "Ayşe"];

// sayılar.push("Hakan"); // HATA! Sayı dizisine yazı eklenemez.

3. Enums (Numaralandırmalar)

Projelerimizde sabit değerler kümesi oluşturmak için enum kullanırız. Örneğin bir siparişin durumları (Hazırlanıyor, Kargoda, Teslim Edildi) veya kullanıcı rolleri (Admin, Editör, Üye) sabit ve bellidir. Kodun okunabilirliğini muazzam derecede artırır.

🎨 Uygulama ve Örnek

enum SiparişDurumu {
    Hazırlanıyor, // Otomatik olarak 0 değerini alır
    Kargoda,       // 1 değerini alır
    TeslimEdildi   // 2 değerini alır
}

let mevcutSipariş = SiparişDurumu.Kargoda;

if (mevcutSipariş === SiparişDurumu.Kargoda) {
    console.log("Siparişiniz yolda!");
}

BÖLÜM 5: Güvenlik ve Boşluk (Null/Undefined) Yönetimi

JavaScript projelerinde en çok karşılaşılan çökme sebebi TypeError: Cannot read properties of undefined hatasıdır. TypeScript bu hatayı kalıcı olarak çözmek için harika araçlar sunar.

1. Null ve Undefined Arasındaki Farklar

2. İsteğe Bağlı Zincirleme (Optional Chaining - ?.)

Bir nesnenin içindeki bir özelliğin var olup olmadığından emin değilsek programın çökmesini engeller. Eğer o özellik yoksa kod çalışmayı durdurur ve güvenli bir şekilde undefined döndürür.

🎨 Uygulama ve Örnek

let müşteri: { isim: string; fatura?: { tutar: number } } = {
    isim: "Ece"
    // fatura bilgisi henüz yok!
};

// Eski usul uzun kontrol:
if (müşteri.fatura && müşteri.fatura.tutar) { ... }

// TypeScript ile Güvenli Yol (Optional Chaining):
console.log(müşteri.fatura?.tutar); // Çıktı: undefined (Program çökmedi!)

3. Null Birleştirme Operatörü (Nullish Coalescing - ??)

Eğer bir değer null veya undefined ise, onun yerine varsayılan (yedek) bir değer koymamızı sağlar.

🎨 Uygulama ve Örnek

let gelenFiyat: number | null = null;

// Eğer gelenFiyat boşsa yedek olarak 100 TL al
let nihaiFiyat = gelenFiyat ?? 100; 
console.log(nihaiFiyat); // Çıktı: 100

4. strictNullChecks ve Kesin Olmayan İşaretçiler (!)


BÖLÜM 6: Özel Tipler ve Esneklik Araçları

TypeScript'in gerçek gücü, kendi tiplerimizi yaratmamıza ve esnek yapılar kurmamıza izin vermesinden gelir.

1. any Tipi ve noImplicitAny Bayrağı

2. Birlik Tipleri (Union Types - |)

Bir değişkenin birden fazla farklı tipte değer alabilmesini sağlar.

🎨 Uygulama ve Örnek

// Müşteri kodu sayı da olabilir, metin de olabilir
let müşteriKodu: number | string;

müşteriKodu = 101;     // Doğru
müşteriKodu = "M-202"; // Doğru
// müşteriKodu = true; // HATA! Birlik tiplerinde boolean yok.

3. Sabit Tipler (Literal Types)

Bir değişkenin sadece bizim belirlediğimiz spesifik kelimeleri veya sayıları alabilmesini sağlar.

🎨 Uygulama ve Örnek

let trafikIşığı: "Kırmızı" | "Sarı" | "Yeşil";

trafikIşığı = "Kırmızı"; // Doğru
// trafikIşığı = "Mavi"; // HATA! Sadece yukarıdaki üç kelimeden biri olabilir.

4. Tip Takma Adları (Type Aliases) vs Arayüzler (Interfaces)

Kendi karmaşık veri modellerimizi oluşturmanın iki yolu vardır:

🎨 Uygulama ve Örnek

// Type Aliases Örneği
type KimlikNo = string | number;

// Interface Örneği (Nesneler için en iyisi)
interface Araba {
    marka: string;
    modelYılı: number;
}

const benimArabam: Araba = {
    marka: "Toyota",
    modelYılı: 2022
};

5. Tip Dayatması (Type Assertions)

Bazen TypeScript'e göre bir verinin tipi çok geneldir (örneğin herhangi bir HTML elementi) ama biz onun tam olarak ne olduğunu biliyoruzdur. TypeScript'e "Bu veriyi şu tipte kabul et" talimatı vermek için as anahtar kelimesini kullanırız.

🎨 Uygulama ve Örnek

let gelenVeri: any = "Merhaba Dünya";

// TypeScript'e bunun kesinlikle bir string olduğunu söylüyoruz
let metinUzunluğu = (gelenVeri as string).length;

====================================================================================================

Müfredatımızın bir sonraki dev aşamasıyla devam ediyoruz. Bu bölümde kodlarımızı organize etmeyi, modern fonksiyon yapılarını ve TypeScript'in en gizemli ileri seviye tiplerini (unknown, never, tuples) çözerek uygulamalarla pekiştireceğiz.


BÖLÜM 7: Modüller, İçeri ve Dışarı Aktarma (Modules, Imports/Exports)

Büyük projelerde tüm kodları tek bir dosyaya yazmak imkansızdır. Kodları parçalara bölüp birbirine bağlamak için modül sistemini kullanırız.

1. Dışarı Aktarma (Export) ve İçeri Aktarma (Import)

Bir dosyadaki fonksiyonu, sınıfı (class) veya değişkeni başka bir dosyada kullanabilmek için başına export yazarız. Kullanacak olan dosya ise bunu import ile çeker.

🎨 Uygulama ve Örnek

matematik.ts (Dışarı aktaran dosya):

export function topla(a: number, b: number): number {
    return a + b;
}
export const PI = 3.14;

uygulama.ts (İçeri aktaran dosya):

import { topla, PI } from "./matematik";

console.log(topla(5, 10)); // Çıktı: 15
console.log(PI); // Çıktı: 3.14

2. Modül Yeniden Aktarımı ve Import Barrel Yapısı

Projede onlarca farklı küçük dosya olduğunda, bunları tek tek import etmek kod kalabalığı yaratır. Bunun yerine tüm küçük dosyaları tek bir index.ts dosyasında toplayıp oradan topluca dışarı fırlatırız. Buna Barrel (Varil) Yapısı denir.

🎨 Uygulama ve Örnek

servisler/index.ts (Barrel Dosyası):

export * from "./kullaniciServisi";
export * from "./urunServisi";
export * from "./faturaServisi";

Böylece başka bir dosyada çağırmak istediğimizde tek tek uğraşmak yerine sadece klasörü hedef alabiliriz:

import { kullaniciBul, urunEkle } from "./servisler";

3. Varsayılan Dışarı Aktarma (Default Export) ve as İle İsim Değiştirme

🎨 Uygulama ve Örnek

// Varsayılan dışarı aktarma örneği
// veriTabani.ts -> export default class VeriTabani {}
import VeriTabani from "./veriTabani"; 

// 'as' kullanarak isim değiştirme
import { topla as toplamaIşlemi } from "./matematik";
console.log(toplamaIşlemi(3, 4));

BÖLÜM 8: Sık Kullanılan Dil Özellikleri ve Modern JavaScript/TypeScript Yapıları

Bu kısımda modern kod yazım standartlarını ve tarayıcı/sunucu üzerinde kodların nasıl adım adım hata ayıklamasının (debugging) yapıldığını öğreniyoruz.

1. Ok Fonksiyonları (Arrow Functions) ve this Bağlamı

Geleneksel fonksiyonlar ile ok fonksiyonları (() => {}) arasındaki en büyük fark this kelimesinin çalışma mantığıdır. Geleneksel fonksiyonlar kendi this bağlamını oluştururken, ok fonksiyonları doğduğu (içinde bulunduğu) üst kapsamın this değerini miras alır.

2. Nesne ve Dizi Dağıtma / Parçalama (Spread & Destructuring)

🎨 Uygulama ve Örnek

// Destructuring ve Spread Örneği
const kitap = { ad: "Nutuk", yazar: "Atatürk", sayfa: 600 };

// Nesne Parçalama (Destructuring)
const { ad, yazar } = kitap; 
console.log(ad); // Çıktı: Nutuk

// Nesne Dağıtma ve Kopyalama (Spread)
const güncellenmişKitap = { ...kitap, fiyat: 150 };

3. Hata Ayıklama (Debugging)

Yazdığımız TypeScript kodlarında bir hata olduğunda, kodun satır satır nasıl çalıştığını izlemek için tarayıcıda veya Node.js ortamında Source Maps (Kaynak Haritaları) kullanılır. tsconfig.json içinde sourceMap: true yapılarak, tarayıcıda doğrudan orijinal .ts dosyamız üzerinde durdurma noktaları (breakpoint) koyup adım adım inceleme yapabiliriz.


BÖLÜM 9: Fonksiyonların Derinlikleri (Functions In Depth)

TypeScript'te fonksiyonlar sadece iş yapan yapılar değildir; parametrelerinin ve dönüş değerlerinin tipleriyle sıkı birer güvence altındadır.

1. Fonksiyon Tipleri ve Özel Fonksiyon Tipleri Tanımlama

Bir fonksiyonun kendisini de bir tip olarak tanımlayabilir ve başka yerlere parametre olarak güvenle aktarabiliriz.

🎨 Uygulama ve Örnek

// İki sayı alıp bir sayı döndüren fonksiyon tipi tanımladık
type MatematikselIşlem = (x: number, y: number) => number;

const carpmaIşlemi: MatematikselIşlem = (a, b) => a * b;
const bolmeIşlemi: MatematikselIşlem = (a, b) => a / b;

BÖLÜM 10: İleri Seviye TypeScript Tipleri (Advanced Language Features)

Geldik dilin en can alıcı, mülakatlarda sıkça sorulan ve ileri düzey projelerin omurgasını oluşturan tiplerine.

1. Tuples (Demetler)

Boyutu ve içindeki elemanlarının sırası, tipi kesin olarak belirlenmiş özel dizilerdir.

🎨 Uygulama ve Örnek

// İlk elemanı kesinlikle sayı, ikincisi kesinlikle metin olan bir Tuple
let koordinat: [number, string];

koordinat = [34, "İstanbul"]; // Doğru
// koordinat = ["Ankara", 06]; // HATA! Sıralama yanlış.

2. unknown Tipi vs any Tipi

🎨 Uygulama ve Örnek

let gelenVeri: unknown = "Merhaba";

// console.log(gelenVeri.toUpperCase()); // HATA! TS der ki: "Dur önce emin ol!"

if (typeof gelenVeri === "string") {
    // Tip daraltıldı, artık güvenle string metotları kullanılabilir
    console.log(gelenVeri.toUpperCase()); 
}

3. Tip Daraltma (Type Narrowing) ve Tip Belirteçleri (Type Predicates)

Bir verinin tipini çalışma anında kontrol ederek (örneğin typeof veya instanceof ile) daha spesifik bir tipe indirme işlemidir. Özel fonksiyonlar yardımıyla kendi tip doğrulayıcılarımızı (is operatörü ile) yazabiliriz.

🎨 Uygulama ve Örnek

interface Kus { uc: () => void }
interface Balik { yuz: () => void }

// Tip Belirteci fonksiyonu (Type Predicate)
function kusMu(hayvan: Kus | Balik): hayvan is Kus {
    return (hayvan as Kus).uc !== undefined;
}

4. never Tipi

Asla gerçekleşmeyecek durumları ifade eder. Örneğin, sürekli hata fırlatan (ve bu yüzden asla sonuca ulaşmayan) bir fonksiyonun dönüş tipi ya da bir switch-case yapısında hiçbir ihtimale kalmadığını gösteren tam güvenli kontrol noktalarında kullanılır.

🎨 Uygulama ve Örnek

function hataFırlat(mesaj: string): never {
    throw new Error(mesaj); // Fonksiyon hiçbir zaman bir şey döndürmez, çöker.
}

5. Kesişim Tipleri (Intersection Types - &)

Birden fazla arayüzü veya tipi birleştirerek yepyeni ve hepsinin özelliklerini içeren tek bir tip oluşturur.

🎨 Uygulama ve Örnek

interface Calisan { id: number; isim: string; }
interface Yonetici { departman: string; }

// İkisini birleştirdik
type Mudur = Calisan & Yonetici;

const yeniMudur: Mudur = {
    id: 5,
    isim: "Zeynep",
    departman: "Yazılım AR-GE"
};



Harika! Hız kesmeden devam ediyoruz. Sırada TypeScript projelerinin beyni olan yapılandırma dosyası (tsconfig.json) ve yazılım mimarilerinin temeli olan Nesne Yönelimli Programlama (OOP) var.

Yine tüm ders sayılarını, dakika ve saniyeleri temizleyerek tamamen saf bilgi, mantık ve kod örneklerine odaklanıyoruz.


BÖLÜM 11: TypeScript Derleyici Ayarları Derinlemesine İnceleme (tsconfig.json)

tsconfig.json dosyası, TypeScript derleyicisine (tsc) kodlarımızı JavaScript'e çevirirken nasıl davranması gerektiğini söyleyen ayar merkezidir.

1. target Özelliği

Yazdığımız modern TypeScript kodlarının, hangi JavaScript versiyonuna (örneğin ES5, ES6/ES2015, ES2022) dönüştürüleceğini belirler.

2. Derlenecek Dosyaları Kontrol Etme (files, include, exclude)

Projedeki her dosyanın derlenmesini istemeyebiliriz. Bunu yönetmek için üç ana ayar vardır:

3. Klasör Yönetimi (outDir ve rootDir)

4. allowJs ve checkJs (JavaScript ile Ortak Çalışma)

Eski bir JavaScript projesini yavaş yavaş TypeScript'e geçiriyorsanız bu ayarlar hayat kurtarır.


BÖLÜM 12: TypeScript ile Nesne Yönelimli Programlama (OOP)

TypeScript, JavaScript'in sınıflar (classes) yapısını çok daha güvenli ve güçlü bir hale getirir.

1. Sınıflar (Classes) ve Yapıcı Metot (Constructor)

Sınıflar, nesne üretmek için kullandığımız kalıplardır. constructor ise o sınıftan yeni bir nesne türetildiğinde (new dendiğinde) otomatik olarak ilk çalışan metottur.

🎨 Uygulama ve Örnek

class Kurs {
    // Üye Değişkenler (Member Variables)
    başlık: string;
    fiyat: number;

    // Yapıcı Metot
    constructor(kursAdı: string, kursFiyatı: number) {
        this.başlık = kursAdı;
        this.fiyat = kursFiyatı;
    }

    bilgiVer() {
        return `${this.başlık} kursu ${this.fiyat} TL'dir.`;
    }
}

// Sınıftan nesne üretme
const tsKursu = new Kurs("Sıfırdan İleri Seviye TypeScript", 400);
console.log(tsKursu.bilgiVer());

2. Erişim Belirteçleri (Access Modifiers)

Sınıfın içindeki değişkenlere dışarıdan kimlerin ulaşıp ulaşamayacağını kontrol ederiz:

🎨 Uygulama ve Örnek

class BankaHesabı {
    public müşteriAdı: string;
    private bakiye: number; // Dışarıdan kimse doğrudan değiştiremez!

    constructor(ad: string, ilkBakiye: number) {
        this.müşteriAdı = ad;
        this.bakiye = ilkBakiye;
    }

    // Bakiyeyi güvenli değiştirmek için metot
    paraYatır(miktar: number) {
        if (miktar > 0) this.bakiye += miktar;
    }
}

3. Alıcılar ve Ayarlayıcılar (Getters and Setters)

Bir private değişkene erişirken veya onu değiştirirken araya bir kontrol mekanizması (koruma duvarı) koymamızı sağlar.

🎨 Uygulama ve Örnek

class Kullanıcı {
    private _yaş: number = 0;

    // Getter (Yaşı okurken çalışır)
    get yaş(): number {
        return this._yaş;
    }

    // Setter (Yaşı değiştirirken çalışır)
    set yaş(yeniYaş: number) {
        if (yeniYaş < 0) {
            throw new Error("Yaş negatif olamaz!");
        }
        this._yaş = yeniYaş;
    }
}

const k = new Kullanıcı();
k.yaş = 25; // Setter çalışır
console.log(k.yaş); // Getter çalışır

4. Kalıtım (Inheritance) ve extends Anahtar Kelimesi

Bir sınıfın özelliklerini ve metotlarını başka bir sınıfa aktarmasını sağlar. Çocuk sınıf, anne sınıfın her şeyini miras alır ve üzerine yeni özellikler ekleyebilir.

🎨 Uygulama ve Örnek

class Personel {
    constructor(public isim: string) {}
    selam Ver() { return `Merhaba, ben ${this.isim}`; }
}

// Personel sınıfından miras alıyoruz
class Yazılımcı extends Personel {
    constructor(isim: string, public uzmanlık: string) {
        super(isim); // Üst sınıfın constructor'ını çağırmak zorundayız!
    }
}

const dev = new Yazılımcı("Hakan", "TypeScript");
console.log(dev.selamVer()); // Miras alınan metot çalışır

5. Soyut Sınıflar (Abstract Classes)

Doğrudan new anahtar kelimesi ile nesnesi üretilemeyen sınıflardır. Sadece diğer sınıflara rehberlik etmek, ortak bir taban oluşturmak ve şablon çizmek için kullanılırlar.

🎨 Uygulama ve Örnek

abstract class VeriTabanıBağlantısı {
    abstract bağlan(): void; // Çocuk sınıflar bu metodu zorunlu doldurmalı!
    
    kopart() {
        console.log("Bağlantı kesildi.");
    }
}

class MongoBağlantısı extends VeriTabanıBağlantısı {
    bağlan() {
        console.log("MongoDB'ye bağlanıldı.");
    }
}



Harika bir tempo ile ilerliyoruz! Sırada TypeScript'in en üst düzey, en profesyonel tasarım kalıplarını oluşturan iki dev konu var: Generics (Genelleyiciler) ve Decorators (Dekoratörler).

Yine tüm kalabalık süreleri ve ders numaralarını temizleyerek doğrudan kodlama mantığına, en yalın açıklamalara ve uygulamalara odaklanıyoruz.


BÖLÜM 13: TypeScript Generics (Genelleyiciler) Derinlemesine İnceleme

Generics, kod yazarken tek bir veri tipine bağımlı kalmak yerine, fonksiyonların veya sınıfların çalışma anında gönderilen tipe göre şekil almasını sağlayan muazzam bir esneklik aracıdır. any gibi güvensiz değildir; esnekliği tam tip güvenliği ile birleştirir.

1. Generic Fonksiyonlar

Bir fonksiyona parametre gönderirken, onun tipini de bir parametre gibi dinamik olarak gönderebiliriz. Genelde <T> harfi (Type kelimesinin baş harfi) ile temsil edilir.

🎨 Uygulama ve Örnek

Dışarıdan gelen herhangi bir dizinin ilk elemanını döndüren güvenli bir fonksiyon yazalım:

// <T> kullanarak bu fonksiyonun generic (genelleyici) olduğunu belirttik
function ilkElemanıGetir<T>(dizi: T[]): T {
    return dizi[0];
}

// Sayı dizisi gönderiyoruz, TS dönüş tipinin otomatik 'number' olduğunu biliyor
let ilkSayı = ilkElemanıGetir<number>([10, 20, 30]); 

// Metin dizisi gönderiyoruz, TS dönüş tipinin otomatik 'string' olduğunu biliyor
let ilkMetin = ilkElemanıGetir<string>(["Ahmet", "Mehmet"]);

2. Tip Kısıtlamaları (Type Constraints) ve extends Operatörü

Generic yapılarda <T> her şey olabilir (sayı, metin, nesne vb.). Ancak bazen bu esnekliği biraz sınırlandırmak isteriz. Örneğin, "Gelecek olan tip ne olursa olsun, içinde kesinlikle bir length (uzunluk) özelliği bulunmak zorunda" diyebiliriz. Bunu extends ile yaparız.

🎨 Uygulama ve Örnek

interface UzunluğuOlan {
    length: number;
}

// T tipi, UzunluğuOlan arayüzünü karşılamak zorundadır!
function uzunlukYazdır<T extends UzunluğuOlan>(girdi: T): void {
    console.log(`Bu nesnenin uzunluğu: ${girdi.length}`);
}

uzunlukYazdır("Merhaba"); // Doğru, çünkü string ifadelerin length özelliği vardır.
uzunlukYazdır([1, 2, 3]); // Doğru, çünkü dizilerin length özelliği vardır.
// uzunlukYazdır(123); // HATA! Sayıların length özelliği yoktur. TS hemen engeller!

3. keyof Operatörü ile Generic Yapılar

Bir nesnenin içindeki anahtar isimlerini (özellik adlarını) dinamik olarak yakalayıp tip haline getirmek için keyof kullanılır. Böylece bir nesneden değer okurken, olmayan bir anahtar ismini yazmayı tamamen imkansız hale getiririz.

🎨 Uygulama ve Örnek

function nesneÖzelliğiAl<T, K extends keyof T>(nesne: T, anahtar: K) {
    return nesne[anahtar];
}

const araba = { marka: "Honda", model: "Civic", yıl: 2023 };

let marka = nesneÖzelliğiAl(araba, "marka"); // Doğru
// let renk = nesneÖzelliğiAl(araba, "renk"); // HATA! 'renk' araba nesnesinin anahtarlarından biri değil!

BÖLÜM 14: TypeScript Decorators (Dekoratörler) Derinlemesine İnceleme

Decorator'lar (Dekoratörler), sınıfların, metotların veya özelliklerin davranışlarını, onların orijinal kodunu hiç değiştirmeden dışarıdan modifiye etmemizi (süslememizi/özelleştirmemizi) sağlayan çok güçlü tasarım yapılarıdır. Başına @ işareti koyularak çağrılırlar. Angular, NestJS ve TypeORM gibi gelişmiş kütüphanelerin temel omurgası bu yapılara dayanır.

(Not: Dekoratörlerin çalışabilmesi için tsconfig.json içinde experimentalDecorators: true ayarının açık olması gerekir).

1. Metot Dekoratörleri (Method Decorators)

Bir sınıfın içindeki bir fonksiyon (metot) çalışmadan hemen önce veya çalıştıktan hemen sonra araya girip işlem yapmak için kullanılır. En popüler kullanım alanı loglama (kayıt tutma) ve yetki kontrolüdür.

🎨 Uygulama ve Örnek

Bir fonksiyon her çalıştığında konsola otomatik olarak bilgi yazdıran bir @Logla dekoratörü tasarlayalım:

function Logla(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const orijinalMetot = descriptor.value;

    // Orijinal fonksiyonun davranışını değiştiriyoruz
    descriptor.value = function (...args: any[]) {
        console.log(`[LOG]: ${propertyKey} metodu çalıştırıldı. Parametreler: ${JSON.stringify(args)}`);
        return orijinalMetot.apply(this, args); // Orijinal fonksiyonu çalıştır
    };
}

class HesapMakinesi {
    @Logla // Dekoratörü metodun başına taktık!
    toplama(a: number, b: number) {
        return a + b;
    }
}

const hesap = new HesapMakinesi();
hesap.toplama(5, 8); 
// Konsol Çıktısı: [LOG]: toplama metodu çalıştırıldı. Parametreler: [5,8]

2. Sınıf ve Özellik Dekoratörleri (Class & Property Decorators)




Müfredatımızın en heyecan verici, en büyük ve pratik kısmına geldik! Bu bölümde teoride öğrendiğimiz tüm TypeScript harikalarını bir araya getirerek, Express ve TypeORM altyapısıyla çalışan, gerçek bir veri tabanına bağlı, güvenli bir Node.js REST API projesi inşa ediyoruz.

Yine tüm gereksiz dakika, saniye ve ders sayılarını temizleyerek doğrudan mimariye, kodlama mantığına ve uygulama adımlarına odaklanıyoruz.


BÖLÜM 15: Pratik API Projesi - Altyapının Kurulması (Node, Express, Hot-Reload)

Gerçek dünya projelerinde sistem sıfırdan adım adım kurulur. Biz de projemizi ilk komutla ayağa kaldırıyoruz.

1. Projeyi Başlatmak (npm init) ve Express Kurulumu

Node.js tabanlı projemizin ayar dosyası olan package.json dosyasını oluşturmak için terminale ilk komutumuzu yazarız. Ardından web sunucumuzu yönetecek olan Express kütüphanesini ve TypeScript tiplerini ekleriz.

npm init -y
npm install express
npm install -D typescript @types/node @types/express ts-node nodemon

2. İlk Express Sunucusunu TypeScript ile Yazmak

Projemizin ana klasöründe (src/server.ts) ilk sunucumuzu tamamen TypeScript kurallarına uygun olarak ayağa kaldırıyoruz.

🎨 Uygulama ve Örnek

import express, { Request, Response } from 'express';

const app = express();
const PORT = 3000;

// JSON formatındaki verileri okuyabilmek için gömülü parser'ı açıyoruz
app.use(express.json());

// İlk Endpoint (Uç Nokta) tanımı
app.get('/api/selam', (req: Request, res: Response) => {
    res.status(200).json({ mesaj: "TypeScript REST API'den merhaba!" });
});

app.listen(PORT, () => {
    console.log(`Sunucu http://localhost:${PORT} adresinde çalışıyor.`);
});

3. Hot-Reload Düzenlemesi ve Çevre Değişkenleri (dotenv)


BÖLÜM 16: Veri Tabanı Katmanı - TypeORM Entegrasyonu

Projemizin verilerini kalıcı hale getirmek için TypeORM (Object Relational Mapping) kütüphanesini kullanacağız. TypeORM, SQL sorguları yazmakla uğraşmadan, TypeScript sınıfları (Class) yardımıyla veri tabanı tabloları üretmemizi ve yönetmemizi sağlar.

1. TypeORM Modeli (Entity) Tasarlamak

Veri tabanında bir tabloya karşılık gelecek olan sınıfımızı oluştururken, daha önce öğrendiğimiz TypeScript Decorator (Dekoratör) yapılarını kullanırız.

🎨 Uygulama ve Örnek

Kurslarımızı tutacak bir Course (Kurs) tablosu tasarlayalım:

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Lesson } from "./Lesson"; // İlişkili diğer tablo

@Entity({ name: "COURSES" }) // Veri tabanında tablo adı COURSES olacak
export class Course {
    
    @PrimaryGeneratedColumn() // Otomatik artan benzersiz ID
    id: number;

    @Column() // Düz metin kolonu
    title: string;

    @Column()
    description: string;

    // Bire-Çok (One-To-Many) İlişki Modeli: Bir kursun birden fazla dersi olabilir
    @OneToMany(() => Lesson, (lesson) => lesson.course, { eager: true })
    lessons: Lesson[];
}

2. Repositories (Depolar) ile Veri Tabanını Doldurma ve Temizleme Scripts

TypeORM bize verileri çekmek, silmek, eklemek için hazır metotlar sunan Repository yapısını sağlar. Projeyi test ederken veri tabanını sıfırlamak veya içine sahte veriler (seed data) doldurmak için özel scriptler yazarız.


BÖLÜM 17: CRUD İşlemleri ve Gelişmiş Express Endpoint'leri

Veri tabanı bağlantımız hazır olduktan sonra, dış dünyaya bu verileri sunacak olan rotaları (routes) ve uç noktaları (endpoints) inşa ederiz.

1. Veri Getirme (GET) ve Filtreleme (Filtering)

Kullanıcıların veri tabanındaki kursları belirli kriterlere göre filtreleyerek çekmesini sağlarız.

app.get("/api/courses", async (req: Request, res: Response) => {
    // TypeORM ile tüm kursları çek ve ilişkili derslerini de otomatik getir (Eager Fetching)
    const courseRepository = appDataSource.getRepository(Course);
    const courses = await courseRepository.find();
    res.json(courses);
});

2. Veri Güncelleme (PATCH) ve Veri Silme (DELETE)

3. Merkezi Hata Yönetimi (Global Error Handling)

Asenkron (async/await) çalışan Express endpoints'lerinde bir hata oluştuğunda sunucunun tamamen çökmesini engellemek için Express'in varsayılan hata yakalama mekanizmasını ezeriz (override). Özel bir hata yakalama ara yazılımı (middleware) yazarak tüm hataları log kütüphanesi (Winston) ile kayıt altına alırız.




Projemizin en kritik, en can alıcı ve son aşamasına geldik: Yazdığımız Node REST API'yi dış dünyaya karşı tamamen korunaklı hale getirmek. Bu bölümde şifreleri veri tabanına açık metin olarak kaydetmek yerine nasıl kırılması imkansız hale getireceğimizi, JWT (JSON Web Tokens) teknolojisini ve kullanıcıların rollerine göre (Admin, Üye vb.) sayfalara erişimini nasıl kısıtlayacağımızı (Middleware) en ince ayrıntısına kadar işliyoruz.

Yine dakika, saniye ve ders sayılarını temizleyerek tamamen güvenlik mimarisine, saf bilgiye ve kodlama örneklerine odaklanıyoruz.


BÖLÜM 18: Arka Yüz API Güvenliği ve Kullanıcı Yönetimi

Kullanıcı kayıt ve giriş işlemlerini yaparken güvenliğin birinci kuralı: Kullanıcı şifreleri asla veri tabanına düz metin (plain text) olarak kaydedilmez.

1. Şifrelerin Hash'lenmesi ve Parola Özetleri (Password Digests)

Bir hacker veri tabanını ele geçirse bile şifreleri görememelidir. Bunun için Node.js'in içinde gömülü olarak gelen crypto modülünü veya popüler şifreleme yöntemlerini kullanarak şifreleri geri döndürülemez birer özete (hash) dönüştürürüz.

🎨 Uygulama ve Örnek

Kullanıcı kayıt olurken şifresini güvenli bir şekilde hash'leyen TypeScript fonksiyonunu yazalım:

import crypto from 'crypto';

export function sifreHashle(acikSifre: string): string {
    // Rastgele bir tuz (salt) oluşturabilir veya sabit güvenli bir algoritma kullanabiliriz
    const hash = crypto.createHash('sha256'); // SHA-256 algoritması
    hash.update(acikSifre);
    return hash.digest('hex'); // Geriye kırılması imkansız 64 karakterli bir metin döner
}

2. Kullanıcı Kayıt (POST /api/users) Endpoint Uygulaması

Kullanıcıdan gelen ad, e-posta ve şifre bilgilerini alıp, şifreyi yukarıdaki fonksiyonla şifreledikten sonra TypeORM yardımıyla veri tabanına kaydederiz.

import { Request, Response } from 'express';
import { User } from './entities/User'; // TypeORM Kullanıcı Tablosu

export const kullaniciKayit = async (req: Request, res: Response) => {
    const { email, password, name } = req.body;

    const userRepository = appDataSource.getRepository(User);
    
    // Şifreyi hash'liyoruz
    const guvenliSifre = sifreHashle(password);

    const yeniKullanici = userRepository.create({
        email,
        password: guvenliSifre,
        name,
        role: "USER" // Varsayılan olarak düz üye rolü veriyoruz
    });

    await userRepository.save(yeniKullanici);
    res.status(201).json({ mesaj: "Kullanıcı başarıyla oluşturuldu." });
};

BÖLÜM 19: JWT (JSON Web Tokens) ile Kimlik Doğrulama

Kullanıcı başarılı bir şekilde giriş yaptığında, sunucu ona bir oturum açmak yerine bir JWT (Token) verir. Kullanıcı sonraki tüm isteklerde bu token'ı yanında taşır ve sunucuya kim olduğunu kanıtlar.

1. JWT Nedir ve Nasıl İmzalanır?

JWT üç parçadan oluşur: Header (Başlık), Payload (Taşınan Veri) ve Signature (İmza). jsonwebtoken kütüphanesini kullanarak kullanıcının ID'sini ve rolünü içeren güvenli bir token imzalarız.

🎨 Uygulama ve Örnek

import jwt from 'jsonwebtoken';

const JWT_SECRET_KEY = "SÜPER_GİZLİ_ANAHTAR_KODUMUZ";

export function tokenOlustur(kullaniciId: number, rol: string): string {
    // Kullanıcının bilgilerini token içine güvenle gömüyoruz
    return jwt.sign(
        { id: kullaniciId, role: rol }, 
        JWT_SECRET_KEY, 
        { expiresIn: '1h' } // Token 1 saat sonra otomatik olarak iptal olur
    );
}

2. Giriş Yapma (Login) ve Şifre Doğrulama Uç Noktası

Kullanıcı giriş yapmak istediğinde e-postası veri tabanında aranır. Gelen şifre tekrar hash'lenerek veri tabanındaki şifreyle karşılaştırılır. Eşleşirse kullanıcıya token teslim edilir.


BÖLÜM 20: Express Ara Yazılımları (Middleware) ve Rol Tabanlı Yetkilendirme

API'mizdeki bazı yollara (örneğin kurs silme veya kurs ekleme) herkesin erişmesini istemeyiz. Sadece giriş yapmış kişilerin ve özellikle ADMIN rolüne sahip kişilerin erişebilmesi için ara yazılımlar (middleware) yazarız.

1. Kimlik Doğrulama Ara Yazılımı (Authentication Middleware)

Gelen isteklerin başlığındaki (Header) token'ı kontrol eden ve geçerliyse isteğin devam etmesine izin veren koruma kapısıdır.

🎨 Uygulama ve Örnek

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

// Express Request tipini genişleterek içine kullanıcı bilgisini ekleyebiliyoruz
export interface KimlikliIstek extends Request {
    kullanici?: any;
}

export function kimlikDogrula(req: KimlikliIstek, res: Response, next: NextFunction) {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN_KODU

    if (!token) {
        return res.status(401).json({ hata: "Erişim engellendi. Token bulunamadı!" });
    }

    try {
        const dogrulanmisVeri = jwt.verify(token, "SÜPER_GİZLİ_ANAHTAR_KODUMUZ");
        req.kullanici = dogrulanmisVeri; // Kullanıcı bilgilerini isteğe ekledik
        next(); // Her şey yolunda, bir sonraki fonksiyona geçebilirsin!
    } catch (hata) {
        res.status(403).json({ hata: "Geçersiz veya süresi dolmuş token!" });
    }
}

2. Rol Tabanlı Yetkilendirme (Role-Based Authorization)

Kimliği doğrulanan kullanıcının, o işlemi yapmaya yetkisinin olup olmadığını denetleriz. Örneğin bir kursu sadece ADMIN silebilir.

🎨 Uygulama ve Örnek

export function rolYetkisiVer(gerekliRol: string) {
    return (req: KimlikliIstek, res: Response, next: NextFunction) => {
        if (!req.kullanici || req.kullanici.role !== gerekliRol) {
            return res.status(403).json({ hata: `Bu işlemi yapabilmek için ${gerekliRol} olmalısınız!` });
        }
        next(); // Yetki var, devam et!
    };
};

// Express Rotasında Kullanımı:
// Bir kursu silmek için önce kimlik doğrulanmalı, sonra ADMIN rolü aranmalı!
app.delete("/api/courses/:id", kimlikDogrula, rolYetkisiVer("ADMIN"), async (req, res) => {
    // Kurs silme kodları...
    res.json({ mesaj: "Kurs başarıyla silindi." });
});

Bu talebiniz doğrultusunda, öğrendiğimiz tüm mimariyi test edeceğimiz pratik bir senaryo hazırladım. Hemen ardından en kapsamlı konuları içeren geniş bir test arayüzü ve en son aşamada ise tüm kritik konuların derinlemesine kod örneklerini bulacaksınız.


🚀 1. JWT YETKİLENDİRME TEST SENARYOSU

Yazdığımız JWT ve Rol Tabanlı Yetkilendirme (Role-Based Authorization) sisteminin gerçek hayatta nasıl test edildiğini simüle eden bir kodlama senaryosu oluşturalım. Bu senaryoda popüler bir test kütüphanesi olan Jest ve HTTP isteklerini simüle eden Supertest kullanılmıştır.

tests/auth.test.ts

import request from 'supertest';
import express from 'express';
import { kimlikDogrula, rolYetkisiVer, KimlikliIstek } from '../src/authMiddleware';
import { tokenOlustur } from '../src/jwtUreteci';

const app = express();
app.use(express.json());

// Test amaçlı korumalı bir rota (Sadece ADMIN silebilir)
app.delete("/api/test-kurs/:id", kimlikDogrula, rolYetkisiVer("ADMIN"), (req, res) => {
    res.status(200).json({ mesaj: "Kurs silindi" });
});

describe("🔐 JWT ve Rol Tabanlı Yetkilendirme Testleri", () => {
    
    it("❌ Token gönderilmediğinde 401 (Yetkisiz) hatası vermelidir", async () => {
        const response = await request(app).delete("/api/test-kurs/1");
        
        expect(response.status).toBe(401);
        expect(response.body.hata).toContain("Token bulunamadı");
    });

    it("❌ Geçersiz bir token gönderildiğinde 403 (Yasak) hatası vermelidir", async () => {
        const response = await request(app)
            .delete("/api/test-kurs/1")
            .set("Authorization", "Bearer gecersiz_token_kodu_123");
            
        expect(response.status).toBe(403);
        expect(response.body.hata).toContain("Geçersiz veya süresi dolmuş token");
    });

    it("❌ Giriş yapan kullanıcı USER rolündeyse, ADMIN rotasına eriştiğinde 403 hatası vermelidir", async () => {
        // Normal kullanıcı için token üretiyoruz
        const userToken = tokenOlustur(99, "USER");

        const response = await request(app)
            .delete("/api/test-kurs/1")
            .set("Authorization", `Bearer ${userToken}`);
            
        expect(response.status).toBe(403);
        expect(response.body.hata).toContain("ADMIN olmalısınız");
    });

    it("✅ Giriş yapan kullanıcı ADMIN rolündeyse, rota başarıyla çalışmalıdır (200 OK)", async () => {
        // Admin kullanıcısı için token üretiyoruz
        const adminToken = tokenOlustur(1, "ADMIN");

        const response = await request(app)
            .delete("/api/test-kurs/1")
            .set("Authorization", `Bearer ${adminToken}`);
            
        expect(response.status).toBe(200);
        expect(response.body.mesaj).toBe("Kurs silindi");
    });
});

📝 2. TÜM MÜFREDATI KAPSAYAN GENİŞ QUIZ (SINAV)

TypeScript'in temel tiplerinden başlayıp, tsconfig.json ayarlarına, OOP prensiplerine, Generics ve Decorators konularına kadar uzanan geniş kapsamlı akademik testinizi aşağıdan çözmeye başlayabilirsiniz.

TypeScript'te aşağıdaki ifadelerden hangisi boolean türündeki bir değişkene doğru bir atama yapar?

[]

  1. let isActive: boolean = "true";

    Yanlış. "true" bir string (metin) değeridir, boolean değildir.

  2. let isActive: boolean = 1;

    Yanlış. 1 sayısal (number) bir değerdir, strict mode altında boolean değişkenine atanamaz.

  3. let isActive: boolean = false;

    Doğru! false değeri tam olarak boolean ilkel tipine karşılık gelir.

  4. let isActive: boolean = null;

    Yanlış. Katı null denetimleri (strictNullChecks) açıkken null değeri boolean türüne atanamaz.

İpucu:

İlkel (primitive) boolean değerleri sadece true veya false ifadelerini alabilir.

Aşağıdaki kod parçasında TypeScript derleyicisi score değişkeninin türünü hangi yöntemle belirler?
let score = 95;

[]

  1. Tip çıkarımı (Type Inference) kullanarak türü otomatik olarak number yapar.

    Doğru! Değer ataması yapıldığı an açık tip belirtilmemişse TypeScript değere bakarak türü çıkarır.

  2. Açık tip belirtimi (Explicit Annotation) zorunludur.

    Yanlış. Değer ataması yapılan durumlarda tip belirtmek zorunlu değildir.

  3. Değişkenin türü varsayılan olarak any olur.

    Yanlış. İlk atanan değere (95) dayanarak türü number olarak belirlenir, any olmaz.

  4. Derleme hatası verir çünkü tip belirtilmemiştir.

    Yanlış. TypeScript tip çıkarımı sayesinde açık tip bildirimine gerek duymaz.

İpucu:

Derleyicinin sağlanan ilk değere bakarak türü tahmin etme mekanizmasını düşünün.

tsconfig.json dosyasında "strictNullChecks": true aktif olduğunda, aşağıdaki kod satırı neden hata verir?
let username: string = null;

[]

  1. string türü asla metin kabul etmez.

    Yanlış. string türü metin değerlerini kabul eder.

  2. null değeri, strictNullChecks aktifken string türüne atanamaz.

    Doğru! Bu ayar açıkken null ve undefined sadece kendi türlerine veya açıkça belirtilmiş union türlerine atanabilir.

  3. Değişken const ile tanımlanmalıdır.

    Yanlış. Hatanın sebebi let veya const kullanımı değil, null atamasıdır.

  4. Değişkenin adı büyük harfle başlamalıdır.

    Yanlış. İsimlendirme kuralları derleme hatasına sebep olmaz.

İpucu:

strictNullChecks ayarı null ve undefined değerlerinin diğer türlere sızmasını engeller.

any ve unknown tipleri arasındaki temel güvenlik farkı aşağıdakilerden hangisidir?

[]

  1. İkisi de tamamen aynı şekilde çalışır, fark sadece isimdedir.

    Yanlış. unknown güvenli bir türdür, any ise tüm tip denetimlerini devre dışı bırakır.

  2. any türündeki değişkenler üzerinde işlem yaparken derleyici hata verir.

    Yanlış. any her türlü işleme izin verir ve hata vermez.

  3. unknown türü sadece sayılar için kullanılır.

    Yanlış. unknown herhangi bir değer alabilir, sayıya özel değildir.

  4. unknown türündeki bir değişkene erişmeden önce tip kontrolü (type narrowing) yapılması gerekirken, any tüm denetimleri atlar.

    Doğru! unknown, any tipinin tür güvenli (type-safe) alternatifidir.

İpucu:

unknown kullandığınızda üzerinde doğrudan metod çağıramazsınız, önce türünü doğrulamanız gerekir.

TypeScript'te bir tuple tanımı olan let user: [string, number] = ["Ali", 25]; ifadesi için aşağıdakilerden hangisi yanlıştır?

[]

  1. İlk eleman string türünde olmalıdır.

    Yanlış. Tanıma göre ilk eleman kesinlikle string olmalıdır, bu ifade doğrudur.

  2. İkinci eleman number türünde olmalıdır.

    Yanlış. Tanıma göre ikinci eleman number olmalıdır, bu ifade doğrudur.

  3. Dizinin eleman sırası ve uzunluğu esnektir, istenildiği kadar eleman eklenebilir.

    Doğru! Bu ifade yanlıştır; Tuple'lar sabit uzunlukta ve belirli sırada tiplere sahiptir.

  4. Sabit sayıda eleman barındırır.

    Yanlış. Bu ifade doğrudur, tuple yapıları sabit sayıda öğe içerir.

İpucu:

Tuple yapıları, her bir pozisyonun türünün önceden bilindiği sabit yapılı dizilerdir.

Sayısal (Numeric) bir enum tanımı yapıldığında TypeScript arka planda nasıl bir varsayılan indeksleme yapar?
enum Direction { Up, Down, Left, Right }

[]

  1. 0'dan başlayarak sırasıyla artan sayısal değerler atar (Up=0, Down=1...).

    Doğru! Sayısal enum'lar ilk elemandan (veya belirtilen başlangıçtan) itibaren otomatik olarak artan değerler alır.

  2. Hepsine rastgele sayılar atar.

    Yanlış. Atamalar rastgele değil, sıralı ve deterministiktir.

  3. Hepsine boş string atar.

    Yanlış. Sayısal enum'lar sayısal değerler barındırır.

  4. Sadece tersine eşleme (reverse mapping) yapmaz.

    Yanlış. Sayısal enum'lar tersine eşlemeyi (sayıdan isme) otomatik olarak destekler.

İpucu:

Değer ataması yapılmadığında sayısal enum'lar sıfırdan başlar.

type (Type Alias) ve interface yapıları arasındaki önemli farklardan biri aşağıdakilerden hangisidir?

[]

  1. Interface'ler sadece fonksiyonlar için kullanılır.

    Yanlış. Interface'ler nesne yapılarını modellemek için yaygın olarak kullanılır.

  2. Interface'ler aynı isimle tekrar tanımlandığında birleştirilebilir (declaration merging), ancak type alias'lar tekrar tanımlanamaz.

    Doğru! Deklarasyon birleştirme (declaration merging) özelliği yalnızca interface'lere özgüdür.

  3. Type alias'lar nesne tanımlayamaz.

    Yanlış. Type alias'lar da nesne türlerini tanımlayabilir.

  4. Interface'ler union veya intersection türlerini temsil edemez.

    Yanlış. Interface'ler extends ile birleştirilebilir ancak union (|) sözdizimi doğrudan type alias'larda daha esnektir.

İpucu:

Aynı isimle iki kez tanımlandığında hata vermeyip özellikleri birleştiren yapı hangisidir?

TypeScript'te bir sınıf içerisindeki özelliklerin (properties) açıkça belirtilmediği durumda varsayılan erişim belirteci (access modifier) hangisidir?

[]

  1. private

    Yanlış. private üyeler sadece tanımlandığı sınıf içinden erişilebilirdir, varsayılan değildir.

  2. protected

    Yanlış. protected alt sınıflardan erişimi ifade eder, varsayılan değildir.

  3. public

    Doğru! Herhangi bir erişim belirteci yazılmadığında üyeler varsayılan olarak public kabul edilir.

  4. readonly

    Yanlış. readonly bir erişim belirteci değil, değiştirilemezlik kısıtlamasıdır.

İpucu:

Eğer bir sınıf üyesini gizlemiyorsanız dışarıya açıktır.

protected erişim belirteci ile tanımlanmış bir sınıf üyesine nerelerden erişilebilir?

[]

  1. Sadece sınıfın dışından ve türetilmiş sınıflardan.

    Yanlış. Sınıfın dışından protected üyeye erişilemez.

  2. Herhangi bir yerden kısıtlama olmaksızın.

    Yanlış. Kısıtlamasız olan public türüdür.

  3. Sadece aynı dosyadaki bağımsız fonksiyonlardan.

    Yanlış. Sınıf dışındaki bağımsız fonksiyonlar protected üyeleri göremez.

  4. Sadece tanımlandığı sınıfın içinden ve o sınıftan türetilen alt sınıflardan.

    Doğru! protected üyeler miras alınan (child) sınıflar ve kendi sınıfı tarafından erişilebilirdir.

İpucu:

Miras alma (inheritance) ilişkisini ve alt sınıfları düşünün.

Soyut sınıflar (abstract classes) hakkında aşağıdakilerden hangisi doğrudur?

[]

  1. Doğrudan new anahtar kelimesi ile örneklenemezler (instantiate edilemezler).

    Doğru! Soyut sınıflar yalnızca alt sınıflar için bir temel/şablon görevi görür ve doğrudan new ile çağrılamazlar.

  2. İçlerinde gövdeli normal metotlar bulunamaz.

    Yanlış. Soyut sınıflar hem gövdesiz soyut metotlar hem de normal gövdeli metotlar içerebilir.

  3. Tüm metotları mutlaka abstract olmak zorundadır.

    Yanlış. İstendiği kadar normal metot eklenebilir, zorunlu değildir.

  4. Sadece JavaScript nesnelerini dönüştürmek için kullanılırlar.

    Yanlış. Nesne dönüşümüyle birincil bir ilgisi yoktur, OOP kalıbıdır.

İpucu:

Soyut sınıfın somutlaştırılıp somutlaştırılamayacağını hatırlayın.

function logLength<T extends { length: number }>(arg: T): void imzasına sahip bir generic fonksiyonda extends { length: number } ifadesinin amacı nedir?

[]

  1. Fonksiyona sadece number türünde parametre geçilmesini zorunlu kılar.

    Yanlış. Tür kısıtlaması parametrenin number olmasını değil, bir length özelliğine sahip olmasını sağlar.

  2. Geçilen argümanın mutlaka bir length özelliğine sahip olmasını (örneğin dizi veya string) garanti altına alır.

    Doğru! Bu kısıtlama sayesinde arg.length ifadesi güvenle kullanılabilir.

  3. Geri dönüş değerinin uzunluğunu 0 yapar.

    Yanlış. Geri dönüş değeri üzerinde bir uzunluk ataması yapmaz.

  4. T türünü tamamen ortadan kaldırır.

    Yanlış. T türünü kaldırmaz, aksine T'nin taşıması gereken asgari yapıyı sınırlar.

İpucu:

Süslü parantez içerisindeki { length: number } yapısının hangi nesnelerde bulunduğunu düşünün.

TypeScript'te standart bir metot dekoratörü (Method Decorator) fonksiyonu normal şartlarda kaç parametre alır?

[]

  1. Sadece 1 parametre (değer)

    Yanlış. Tek parametre almaz.

  2. 4 parametre

    Yanlış. Standart metot dekoratörleri 3 adet parametre alır.

  3. 3 parametre: (target, propertyKey, descriptor)

    Doğru! Sırasıyla prototype/constructor, metot adı ve property descriptor parametrelerini alır.

  4. 2 parametre: (target, propertyKey)

    Yanlış. 2 parametre property (özellik) dekoratörleri için geçerlidir.

İpucu:

Metot descriptor bilgisini içeren imza yapısını hatırlayın.

tsconfig.json dosyasındaki "target": "ES6" ve "outDir": "./dist" ayarlarının işlevleri sırasıyla aşağıdakilerden hangisidir?

[]

  1. Derlenen JavaScript kodunun ES6 sürümüne çevrilmesini ve çıktıların ./dist klasörüne kaydedilmesini sağlar.

    Doğru! target çıktı JS sürümünü, outDir ise derlenmiş dosyaların hedef klasörünü belirler.

  2. Kaynak kodların ES6 sürümünde yazılmasını zorunlu kılar ve girdi klasörünü belirler.

    Yanlış. target girdi dilini değil çıktı JS sürümünü belirler; girdi için rootDir kullanılır.

  3. Sadece hata ayıklama modu ve kaynak haritaları (source map) oluşturur.

    Yanlış. Bu ayarlar hata ayıklama moduyla doğrudan ilgili değildir.

  4. Projedeki paketleri NPM deposuna gönderir.

    Yanlış. NPM yayınlama işlemi tsconfig.json target ve outDir ayarlarıyla yapılmaz.

İpucu:

target derleme çıktısının ECMAScript sürümünü, outDir ise nereye yazılacağını belirtir.

TypeScript'te bir union türündeki (string | number) değişkenin o anki çalışma zamanı (runtime) gerçek türünü kontrol etmek için yaygın olarak kullanılan operatör hangisidir?

[]

  1. instanceof

    Yanlış. instanceof sınıflar (classes) için kullanılır, ilkel (primitive) tiplerde string veya number ayırt etmek için yetersizdir.

  2. checkType

    Yanlış. checkType diye bir JavaScript/TypeScript operatörü yoktur.

  3. is

    Yanlış. is özel tip koruma fonksiyonlarında (type predicate) kullanılır, temel operatör değildir.

  4. typeof

    Doğru! typeof x === 'string' gibi kontrollerle tip daraltması (type narrowing) yapılır.

İpucu:

İlkel tiplerin türünü sorgulamak için JavaScript'ten beri gelen operatörü düşünün.

interface User { id: number; name: string; } tanımlı bir arayüzün tüm alanlarını isteğe bağlı (optional) hale getiren yardımcı tip (Utility Type) aşağıdakilerden hangisidir?

[]

  1. Required<User>

    Yanlış. Required tüm alanları zorunlu hale getirir, opsiyonel yapmaz.

  2. Partial<User>

    Doğru! Partial<T>, T türündeki tüm özellikleri isteğe bağlı (optional) yapar.

  3. Readonly<User>

    Yanlış. Readonly alanları sadece okunabilir (değiştirilemez) yapar.

  4. Pick<User, "id">

    Yanlış. Pick arayüzden yalnızca belirli alanları seçer.

İpucu:

"Parçalı" veya "kısmi" anlamına gelen İngilizce kelimeden türetilmiş yardımcı tipi hatırlayın.

TypeScript Kapsamlı Bilgi ve Mimari Sınavı

[]


💻 3. TÜM KRİTİK KONULARIN DERİNLEMESİNE KOD ÖRNEKLERİ

Tüm müfredat boyunca işlediğimiz en karmaşık yapıların endüstriyel standartlardaki derinlemesine ve ileri seviye kod örneklerini aşağıda inceleyebilirsiniz.

A. İleri Seviye Tip Daraltma (Type Narrowing) ve Ayrıştırılmış Birlikler (Discriminated Unions)

Gerçek hayatta API'lerden farklı formatlarda veri gelebilir. Bu verileri türlerine göre en güvenli şekilde ayırmak için sabit bir type veya kind alanı kullanırız.

interface BasariliOdeme {
    durum: "basarili"; // Discriminated alan (Ayırt edici literal)
    islemId: string;
    tutar: number;
}

interface HataliOdeme {
    durum: "hatali";
    hataKodu: number;
    mesaj: string;
}

type OdemeSonucu = BasariliOdeme | HataliOdeme;

function odemeMesajiniYazdir(sonuc: OdemeSonucu): string {
    // TypeScript 'durum' alanına bakarak if/switch içinde tipleri otomatik daraltır
    switch (sonuc.durum) {
        case "basarili":
            return `Tebrikler! ${sonuc.islemId} kodlu işlem ile ${sonuc.tutar} TL alındı.`;
        case "hatali":
            return `Ödeme başarısız. Hata Kodu: ${sonuc.hataKodu}. Nedeni: ${sonuc.mesaj}`;
        default:
            // exhaustiveness check: Asla buraya düşmemeli (never tipi kontrolü)
            const _kontrol: never = sonuc;
            return _kontrol;
    }
}

B. Gerçekçi Bir Generic Repository Sınıfı Uygulaması

Veri tabanı işlemlerinde her tablo için tek tek ekleme/silme fonksiyonları yazmak yerine, her tipe uyum sağlayan tek bir sınıf tasarlayabiliriz.

interface IdZorunlulugu {
    id: number;
}

// T tipi kesinlikle içinde 'id' barındıran bir nesne olmak zorunda
class VeriDeposu<T extends IdZorunlulugu> {
    private veriler: T[] = [];

    kaydet(yeniVeri: T): void {
        this.veriler.push(yeniVeri);
        console.log("Veri depoya eklendi.");
    }

    idIleBul(id: number): T | undefined {
        return this.veriler.find(item => item.id === id);
    }

    hepsiniGetir(): T[] {
        return this.veriler;
    }
}

// Kullanım örneği:
interface Urun extends IdZorunlulugu {
    ad: string;
    fiyat: number;
}

const urunDeposu = new VeriDeposu<Urun>();
urunDeposu.kaydet({ id: 101, ad: "Laptop", fiyat: 45000 });
console.log(urunDeposu.idIleBul(101)?.ad); // Çıktı: Laptop

C. Parametre Alan Gelişmiş Metot Dekoratörü (Decorator Factory)

Sadece metodu süslemekle kalmayıp, dışarıdan parametre alarak davranışını dinamik olarak değiştiren gelişmiş bir yetki kontrol (@Yetki) dekoratörü tasarlayalım.

// Dışarıdan rol parametresi alan bir Dekoratör Fabrikası
function YetkiKontrolu(gerekenRol: string) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const orijinalMetot = descriptor.value;

        descriptor.value = function (...args: any[]) {
            // Sistemde aktif olan kullanıcının rolünü simüle ediyoruz
            const aktifKullanici = { isim: "Mert", rol: "USER" }; 

            if (aktifKullanici.rol !== gerekenRol) {
                throw new Error(`🚫 GÜVENLİK UYARISI: ${propertyKey} fonksiyonunu tetiklemek için ${gerekenRol} yetkiniz yok!`);
            }

            // Yetki varsa orijinal metot çalışır
            return orijinalMetot.apply(this, args);
        };
    };
}

class AdminPaneli {
    @YetkiKontrolu("ADMIN") // Sadece ADMIN rolü bu metodu çalıştırabilir
    sistemiKapat() {
        console.log("Sistem başarıyla kapatılıyor...");
    }
}

try {
    const panel = new AdminPaneli();
    panel.sistemiKapat(); // Hata fırlatacaktır çünkü Mert'in rolü USER!
} catch (e: any) {
    console.error(e.message);
}



TypeScript’in en profesyonel iki mimari özelliğine odaklanıyoruz: Hata yapmanızı imkansız kılan never tabanlı tam güvenli (exhaustive) kontrol ve kodunuzdaki karmaşık klasör yollarını (../../) temizleyen path mapping.

Gereksiz tüm kalabalık detayları temizleyerek doğrudan saf bilgiye, mantığa ve üretime yönelik kod örneklerine geçiyoruz.


🔎 1. never Tabanlı Tam Güvenli (Exhaustive) Kontrol Mekanizması

Büyük ve sürekli büyüyen projelerde en büyük tehlike, yeni eklenen bir veri tipinin veya seçeneğin (örneğin yeni bir ödeme yöntemi veya kullanıcı rolü) kodun diğer yerlerindeki switch-case veya if-else bloklarında unutulmasıdır.

TypeScript, never tipinin benzersiz doğasını kullanarak bizi derleme anında (kod çalışmadan önce) uyarır. Mantık şudur: never türündeki bir değişkene, kendi dışında hiçbir veri türü atanamaz.

🎨 Derinlemesine Uygulama Örneği

Diyelim ki bir e-ticaret sisteminde kullanıcı rollerini yönetiyoruz.

// 1. Aşama: Rollerimizi Tanımlıyoruz
interface AdminKullanici { role: "admin"; sistemiKapat: () => void; }
interface EditorKullanici { role: "editor"; makaleDuzenle: () => void; }
interface UyeKullanici { role: "uye"; yorumYap: () => void; }

// Kullanıcı birlik (union) türü
type KullaniciTipi = AdminKullanici | EditorKullanici | UyeKullanici;

function yetkiKontrolMerkezi(kullanici: KullaniciTipi) {
    switch (kullanici.role) {
        case "admin":
            kullanici.sistemiKapat();
            break;
        case "editor":
            kullanici.makaleDuzenle();
            break;
        case "uye":
            kullanici.yorumYap();
            break;
        default:
            // 🔥 BURASI GÜVENLİK DUVARIDIR (Exhaustiveness Check)
            // Eğer yukarıdaki tüm ihtimaller (admin, editor, uye) kapsandıysa,
            // 'kullanici' değişkenine geriye hiçbir ihtimal kalmaz, yani tipi 'never' olur.
            const _unuttugumuzIhtimal: never = kullanici;
            return _unuttugumuzIhtimal;
    }
}

⚠️ Kod Nasıl Hayat Kurtarır? (Geliştirme Senaryosu)

Aylar sonra projeye yeni bir özellik geldi ve KullaniciTipi içerisine KuryeKullanici adında 4. bir rol eklendi:

interface KuryeKullanici { role: "kurye"; paketTeslimEt: () => void; }

// Yeni rolü birliğe ekliyoruz
type KullaniciTipi = AdminKullanici | EditorKullanici | UyeKullanici | KuryeKullanici;

Siz bu rolü eklediğiniz an, yetkiKontrolMerkezi fonksiyonundaki default satırı derleme hatası verecektir!

TypeScript Hata Mesajı: Type 'KuryeKullanici' is not assignable to type 'never'.

Neden Hata Verdi? Çünkü switch bloğu kurye ihtimalini kontrol etmedi. Kod default kısmına düştüğünde kullanici artık never değil, KuryeKullanici tipindedir. TypeScript size der ki: "Hey yazılımcı! Yeni bir rol ekledin ama bu fonksiyonda kurye rolünün ne yapacağını yazmayı unuttun. Kodu düzeltmeden projeyi derlemem!"


🛠 2. tsconfig.json İçerisinde Path Mapping (Klasör Kısayolları) Mimarisi

Büyük projelerde klasörler derinleştikçe, üst klasördeki bir dosyayı çağırmak tam bir kabusa dönüşür. Şunun gibi kodlar yazmak zorunda kalırsınız:
import { VeriTabani } from "../../../config/database/db";

Path Mapping (Yol Eşleme) sayesinde bu karmaşık yolları temiz, okunabilir takma adlara (alias) dönüştürürüz:
import { VeriTabani } from "@config/db";

Bu mimariyi kurmak için iki adımlı bir işlem yapmamız gerekir.

Adım 1: tsconfig.json Dosyasının Yapılandırılması

Kısayolların çalışması için compilerOptions altına baseUrl ve paths ayarlarını ekleriz.

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    
    /* 📌 PATH MAPPING AYARLARI */
    "baseUrl": "./", // Kısayolların hangi ana klasörü baz alacağını söyler
    "paths": {
      "@config/*": ["src/config/*"],     // @config/db yazınca src/config/db'ye git
      "@servisler/*": ["src/services/*"], // @servisler/user yazınca src/services/user'a git
      "@modeller/*": ["src/models/*"]     // @modeller/kurs yazınca src/models/kurs'a git
    }
  }
}

Adım 2: Kod İçinde Kullanım Örneği

Artık projenin neresinde olursanız olun, derinlik fark etmeksizin klasörleri şu şekilde tertemiz çağırabilirsiniz:

src/services/userService.ts

// Eski yöntem: import { Kurs } from "../models/kurs";
// Yeni ve Temiz Yöntem:
import { Kurs } from "@modeller/kurs"; 
import { dbBaglantisi } from "@config/database";

export class UserService {
    kurslariListele() {
        console.log("Kısayol mimarisi başarıyla çalışıyor!");
    }
}

💡 Kritik Arka Yüz Notu (Üretim Ortamı Çözümü)

TypeScript derleyicisi (tsc), kodları JavaScript’e (dist klasörüne) çevirirken bu @modeller/* gibi takma adları otomatik olarak düzeltmez, aynen bırakır. Düz JavaScript bu takma adları tanımadığı için üretim ortamında (production) kodunuz Kütüphane bulunamadı hatası verir.

Bunu çözmek için üretilen JavaScript kodundaki yolları otomatik düzelten tsconfig-paths paketini kullanırız:

  1. Paketi kurun: npm install -D tsconfig-paths

  2. package.json dosyanızdaki çalıştırma scriptinizi şu şekilde güncelleyin:

    "scripts": {
      "start": "node -r tsconfig-paths/register dist/server.js",
      "dev": "nodemon -r tsconfig-paths/register src/server.ts"
    }

Bu sayede hem geliştirme yaparken hem de canlı sunucuda kodlarınız hiçbir yol hatası almadan kusursuzca çalışır.




Büyük ölçekli kurumsal projelerde TypeScript’in kendi derleyicisi (tsc) büyük kod kütlelerinde yavaş kalmaya başlar. Bu yüzden günümüz modern mimarilerinde SWC (Speedy Web Compiler) gibi pas tabanlı ultra hızlı derleyiciler veya ön yüz ağırlıklı projelerde Webpack gibi paketleyiciler (bundlers) tercih edilir.

Bu sistemlerde ve kuracağımız @modeller mimarisinde takma adların (path mapping) nasıl senkronize edildiğini en yalın haliyle adım adım inceliyoruz.


🚀 1. SWC ve Webpack Sektörel Senkronizasyonu

A. SWC (Speedy Web Compiler) ile Senkronizasyon

SWC, tsconfig.json dosyasındaki paths ayarlarını doğrudan okumaz. Bu yüzden projeyi SWC ile derlerken takma adların kırılmaması için .swcrc ayar dosyasına bu yolları manuel veya eklentiyle tanıtmak gerekir.

.swcrc Yapılandırması

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true,
      "dynamicImport": true
    },
    "target": "es2022",
    "baseUrl": "./",
    /* SWC'ye yolları tsconfig ile birebir senkronize olacak şekilde öğretiyoruz */
    "paths": {
      "@config/*": ["src/config/*"],
      "@servisler/*": ["src/services/*"],
      "@modeller/*": ["src/models/*"]
    }
  },
  "module": {
    "type": "commonjs"
  }
}

B. Webpack ile Senkronizasyon

Webpack ekosisteminde her şeyi elinizle yazmak yerine tsconfig.json dosyasını otomatik olarak okuyup Webpack'e enjekte eden harika bir eklenti (tsconfig-paths-webpack-plugin) kullanılır. Böylece senkronizasyon tek merkezden (sadece tsconfig'den) yürütülür.

webpack.config.js Yapılandırması

const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const path = require('path');

module.exports = {
  entry: './src/server.ts',
  target: 'node',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
    /* Eklentiyi buraya bağladığımız an Webpack tüm @kısayolları otomatik tanır */
    plugins: [
      new TsconfigPathsPlugin({
        configFile: path.resolve(__dirname, './tsconfig.json')
      })
    ]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

🛠 2. TypeORM Projesinde @modeller Refactor Uygulaması

Mevcut TypeORM projemizdeki karmaşık ve kırılgan göreceli yolları (../../entities/Course) tamamen ortadan kaldırıp, yeni @modeller mimarisine geçiş işlemini (refactoring) gerçekleştiriyoruz.

Adım A: Klasör Düzeni

src/
 ├── config/
 │    └── database.ts
 ├── models/
 │    ├── Course.ts
 │    └── Lesson.ts
 └── server.ts

Adım B: Veri Modellerinin @modeller ile Yeniden Yazılması

src/models/Lesson.ts (Ders Modeli)

Ders modeli, @modeller/Course üzerinden üst kurs tablosuna güvenle bağlanır.

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
// 🎯 REFACTOR: Eski "../models/Course" yerine doğrudan kısayol kullanıyoruz
import { Course } from "@modeller/Course";

@Entity({ name: "LESSONS" })
export class Lesson {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    duration: string;

    // Çoktan-Bire İlişki: Birden fazla ders tek bir kursa ait olabilir
    @ManyToOne(() => Course, (course) => course.lessons)
    course: Course;
}

src/models/Course.ts (Kurs Modeli)

Kurs modeli de aynı şekilde @modeller/Lesson kısayoluyla alt dersleri içeri aktarır.

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
// 🎯 REFACTOR: Eski ve çirkin bağımlılık yolu temizlendi
import { Lesson } from "@modeller/Lesson";

@Entity({ name: "COURSES" })
export class Course {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    description: string;

    // Bire-Çok İlişki: Bir kursun birden çok dersi olabilir
    @OneToMany(() => Lesson, (lesson) => lesson.course, { eager: true })
    lessons: Lesson[];
}

Adım C: Veri Tabanı Bağlantı Dosyasının Güncellenmesi

TypeORM veri tabanını ayağa kaldırırken hangi modelleri tarayacağını bilmek zorundadır. Burada da kısayol gücünü kullanıyoruz.

src/config/database.ts

import { DataSource } from "typeorm";
// 🎯 REFACTOR: Tüm modeller merkezi takma ad yapısından çekiliyor
import { Course } from "@modeller/Course";
import { Lesson } from "@modeller/Lesson";

export const appDataSource = new DataSource({
    type: "postgres",
    host: "localhost",
    port: 5432,
    username: "postgres",
    password: "secret_password",
    database: "bootcamp_db",
    synchronize: true, // Geliştirme aşamasında tabloları otomatik üretir
    logging: false,
    entities: [Course, Lesson], // Temizlenmiş modeller listesi
});

Adım D: Ana Sunucu Dosyasında Kullanım

src/server.ts

import express, { Request, Response } from "express";
import { appDataSource } from "@config/database";
import { Course } from "@modeller/Course";

const app = express();
app.use(express.json());

app.get("/api/courses", async (req: Request, res: Response) => {
    try {
        const repo = appDataSource.getRepository(Course);
        const tumKurslar = await repo.find();
        res.status(200).json(tumKurslar);
    } catch (hata) {
        res.status(500).json({ mesaj: "Veri tabanı hatası oluştu." });
    }
});

// Veri tabanını bağla ve sunucuyu başlat
appDataSource.initialize().then(() => {
    app.listen(3000, () => console.log("🚀 Sunucu port 3000 üzerinde çalışıyor."));
});

Büyük ölçekli projelerde mimariyi oturtmanın son adımı, yazdığımız kuralları otomatik olarak denetleyen sistemlerdir. Projenizin düzenini korumak için takma adları (path mapping) ESLint ve Prettier araçlarına tanıtacağız, ardından kurumsal projelerin vazgeçilmezi olan TypeORM Query Builder ile derinlemesine sorgular yazacağız.

Alandaki karmaşık teorik kalabalığı temizleyerek doğrudan uygulamaya yönelik kodlarımıza geçiyoruz.


🛠 1. ESLint ve Prettier Takma Ad Entegrasyonu (Import Sıralaması)

TypeScript projenizde @modeller gibi kısayollar kullandığınızda, ESLint bu yolları harici bir NPM paketi sanabilir veya nerede bulacağını bilemeyip hata fırlatabilir. Bunu önlemek ve tüm import satırlarını otomatik olarak sıraya dizmek için yapılandırma adımlarını uyguluyoruz.

A. Gerekli Paketlerin Kurulumu

Terminale aşağıdaki komutu yazarak gerekli ESLint eklentilerini kurun:

npm install -D eslint-plugin-import eslint-import-resolver-typescript

B. ESLint Yapılandırması (eslint.config.js veya .eslintrc.json)

Buradaki en kritik ayar eslint-import-resolver-typescript eklentisidir. Bu eklenti ESLint'e gidip tsconfig.json dosyanızdaki paths ayarlarını okumasını söyler.

{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:import/recommended",
    "plugin:import/typescript"
  ],
  "settings": {
    "import/resolver": {
      /* 🎯 ESLint'e tsconfig.json dosyasını okumasını söylüyoruz */
      "typescript": {
        "alwaysTryTypes": true
      }
    }
  },
  "rules": {
    /* 📌 Otomatik Import Sıralama Kuralları (Import Sorting) */
    "import/order": [
      "error",
      {
        "groups": [
          "builtin",    // 1. Node.js gömülü modülleri (fs, path vb.)
          "external",   // 2. Dış kütüphaneler (express, typeorm vb.)
          "internal",   // 3. Bizim @kısayollar ve proje içi dosyalar
          ["sibling", "parent"], // 4. Göreceli yakın dosyalar
          "index"       // 5. Index dosyaları
        ],
        "pathGroups": [
          {
            "pattern": "@**",
            "group": "internal",
            "position": "before"
          }
        ],
        "pathGroupsExcludedImportTypes": ["builtin"],
        "newlines-between": "always", // Gruplar arasında bir satır boşluk bırak
        "alphabetize": {
          "order": "asc", // Harf sırasına göre diz (A-Z)
          "caseInsensitive": true
        }
      }
    ]
  }
}

C. Prettier ile Uyum (.prettierrc)

Prettier, kod stilini düzenlerken ESLint kurallarıyla çatışmamalıdır. Eğer import satırlarının Prettier tarafından da otomatik sıralanmasını istiyorsanız @trivago/prettier-plugin-sort-imports eklentisini kurup şu ayarı ekleyebilirsiniz:

{
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 100,
  "plugins": ["@trivago/prettier-plugin-sort-imports"],
  "importOrder": ["^express$", "^typeorm$", "^@config/(.*)$", "^@modeller/(.*)$", "^[./]"],
  "importOrderSeparation": true,
  "importOrderSortSpecifiers": true
}

📊 2. TypeORM Query Builder ile Derinlemesine Sorgu Manipülasyonu

Bazen TypeORM'un standart find() veya findOne() metotları karmaşık raporlama, performans optimizasyonu veya çoklu tablolardan (JOIN) veri birleştirme işlemleri için yetersiz kalır. Bu durumlarda Query Builder (Sorgu Oluşturucu) kullanarak SQL performansında ama tamamen TypeScript tip güvenliğinde sorgular yazarız.

🎨 Endüstriyel Seviye Query Builder Örneği

Senaryomuz: Dışarıdan gelen arama kelimesine (keyword) göre kurs başlığında arama yapan, sadece aktif dersleri olan kursları getiren, ders sayılarını sayan ve performansı artırmak için sadece belirli kolonları seçen (Select) gelişmiş bir servis fonksiyonu yazalım.

src/services/courseService.ts

import { appDataSource } from "@config/database";
import { Course } from "@modeller/Course";

export class CourseService {
    
    async gelismisKursRaporu(aramaKelimesi: string, minimumDersSayisi: number) {
        // 'course' takma adıyla (alias) sorguyu başlatıyoruz
        const sorguOlusturucu = appDataSource
            .getRepository(Course)
            .createQueryBuilder("course");

        const rapor = await sorguOlusturucu
            /* 1. LEFT JOIN: Kursa ait dersleri bağlıyoruz */
            .leftJoinAndSelect("course.lessons", "lesson")

            /* 2. GÜVENLİ FİLTRELEME (SQL Injection Korumalı) */
            // Doğrudan string birleştirmek yerine ':kelime' parametresi kullanıyoruz
            .where("course.title ILIKE :kelime", { kelime: `%${aramaKelimesi}%` })

            /* 3. EK KOŞUL (AND WHERE) */
            .andWhere("lesson.id IS NOT NULL") // Sadece dersi olan kursları getir

            /* 4. GRUPLAMA VE SEÇİM (İsteğe bağlı kolon filtreleme) */
            // Büyük projelerde devasa metin alanlarını çekmeyerek RAM tasarrufu sağlarız
            .select([
                "course.id",
                "course.title",
                "lesson.id",
                "lesson.title",
                "lesson.duration"
            ])

            /* 5. SIRALAMA (ORDER BY) */
            .orderBy("course.title", "ASC")
            .addOrderBy("lesson.id", "DESC")

            /* 6. SAYFALAMA (PAGINATION) */
            .skip(0)  // İlk sayfadan başla (Offset)
            .take(10) // Maksimum 10 kayıt getir (Limit)

            /* 7. VERİYİ GETİR */
            .getMany(); // İlişkisel nesne mimarisinde (Course[]) veri döner

        return rapor;
    }

    async sadeceIstatistikGetir() {
        /* SQL ham aggregate (COUNT, SUM) fonksiyonlarını kullanma yöntemi */
        const istatistik = await appDataSource
            .getRepository(Course)
            .createQueryBuilder("c")
            .leftJoin("c.lessons", "l")
            .select("c.id", "kursId")
            .addSelect("c.title", "kursBasligi")
            .addSelect("COUNT(l.id)", "toplamDersSayisi") // SQL Count fonksiyonu
            .groupBy("c.id")
            .getRawMany(); // Entity nesnesi değil, düz ham JSON verisi döndürür

        return istatistik;
    }
}

💡 Kodun Avantajları Nedir?


****************************************************************************