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.
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:
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.
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.
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.
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.
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.
Dosyanın ilk ders konularını kapsayan bu bölümde, TypeScript'in temellerine ve neden JavaScript yerine tercih edildiğine odaklanıyoruz.
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.
Hataları Erken Yakalama: Kodunuzu daha çalıştırmadan, yazarken hatalı bir işlem yaptıysanız editörünüz sizi kırmızı çizgilerle uyarır.
Akıllı Kod Tamamlama (Autocompletion): Hangi nesnenin içinde hangi özelliklerin olduğunu ezberlemek zorunda kalmazsınız. Kod editörü size otomatik olarak seçenekleri sunar.
JavaScript Dinamik bir dildir: Değişkenlerin tipi kod çalışırken (runtime) belirlenir ve sürekli değişebilir.
TypeScript Statik bir dildir: Değişkenlerin tipi daha kod yazılırken belirlenir ve sonradan değiştirilemez.
JavaScript ve TypeScript arasındaki farkı net bir şekilde görelim.
JavaScript Dünyası (Hata koruması yok):
letkullanıcıKodu=12345;// Başta bir sayıkullanıcıKodu ="Hakan";// Sonradan metne dönüştü, JavaScript buna kızmaz.functionsepetiHesapla(fiyat,adet) {returnfiyat * 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):
letkullanıcıKodu:number=12345;// kullanıcıKodu = "Hakan"; // TS DERLEYİCİSİ BURADA HATA VERİR: "string tipi number tipine atanamaz!"functionsepetiHesapla(fiyat:number,adet:number):number{returnfiyat * adet;}// console.log(sepetiHesapla("yirmi", 5)); // TS Editörü hemen kırmızı çizer: "yirmi" bir sayı değildir!
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.
noEmitOnError
Özelliği: Normalde TypeScript kodunuzda bir tip hatası olsa
bile derleyici yine de bir JavaScript dosyası üretir. Ancak
tsconfig.json
dosyasında noEmitOnError:
true yaparsanız, kodda tek bir tip hatası
bile varsa derleyici asla JavaScript dosyası üretmez (emit
etmez). "Önce hatalarını düzelt!" der.
Bu kısımda dilin en temel yapı taşları olan ilkel (primitive) veri tiplerini inceliyoruz.
const,
let
ve var
Farklarıvar:
Eski JavaScript'ten kalmadır. Fonksiyon kapsamlıdır
(function-scoped). Blokların (if,
for)
dışına sızabilir. Güvensizdir, modern projelerde kullanılmaz.
let:
Blok kapsamlıdır (block-scoped). Sadece yazıldığı süslü
parantezlerin {}
içinde yaşar. Değeri sonradan değiştirilebilir.
const:
Blok kapsamlıdır. Sabittir. Bir kez değer atandıktan sonra yeni
bir değer atanamaz.
if(true) {varsızanDeğişken="Ben her yere giderim";letgizliDeğişken="Ben sadece bu if içindeyim";constsabitDeğ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.
number,
string,
booleanTypeScript'teki en temel üç veri tipidir.
number:
Tam sayılar, ondalıklı sayılar, negatif sayılar dahil tüm
sayıları kapsar.
string:
Metinsel ifadeleri tutar.
boolean:
Sadece true
(doğru) veya false
(yanlış) değerlerini alır.
letyaş:number=25;letboy:number=1.78;letisim:string="Ahmet";letöğrenciMi:boolean=true;
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.
letürünAdı:string="Kablosuz Kulaklık";letfiyat:number=1500;// Eski yöntem:letmesajEski="Aldığınız "+ ürünAdı +" ürünü "+ fiyat +" TL'dir.";// Şablon Metin Yöntemi (Temiz ve Okunabilir):letmesajYeni:string=`Aldığınız ${ürünAdı} ürünü tam ${fiyat} TL'dir.`;console.log(mesajYeni);
Tip Belirtimi (Type
Annotation): Değişkenin tipini bizim açıkça isim:
string şeklinde yazmamızdır.
Tip Çıkarımı (Type Inference): TypeScript'in çok akıllı olmasıdır. Siz tip yazmasanız bile, atadığınız ilk değerden değişkenin tipini kendisi tahmin eder ve kilitler.
// Tip Belirtimi (Biz söyledik)letşehir:string="Ankara";// Tip Çıkarımı (TS kendisi anladı)letpostaKodu=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.
Bu bölümde TypeScript'in nesneleri, dizileri ve özel veri türlerini nasıl kontrol altına aldığını inceleyeceğiz.
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.
// Basit ve İçiçe Geçmiş Nesne Tanımlamasıletkullanıcı: {id:number;isim:string;adres: {şehir:string;postaKodu:number;}};// Doğru Kullanımkullanıcı = {id:1,isim:"Can",adres: {şehir:"İzmir",postaKodu:35000}};// kullanıcı.id = "iki"; // HATA! Sayı yerine metin girilemez.
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.
letsayılar:number[] =;letisimler:string[] = ["Ahmet","Mehmet","Ayşe"];// sayılar.push("Hakan"); // HATA! Sayı dizisine yazı eklenemez.
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.
enumSiparişDurumu{Hazırlanıyor,// Otomatik olarak 0 değerini alırKargoda,// 1 değerini alırTeslimEdildi// 2 değerini alır}letmevcutSipariş= SiparişDurumu.Kargoda;if(mevcutSipariş === SiparişDurumu.Kargoda) {console.log("Siparişiniz yolda!");}
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.
Null
ve Undefined
Arasındaki Farklarundefined:
Bir değişkenin henüz hiçbir değer almadığını, yani
tanımlanmadığını gösterir.
null:
Bir değişkenin değerinin kasıtlı olarak "boş" veya
"hiçlik" olarak ayarlandığını gösterir.
?.)
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.
letmüş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!)
??)
Eğer bir değer null
veya undefined
ise, onun yerine varsayılan (yedek) bir değer koymamızı sağlar.
letgelenFiyat:number|null=null;// Eğer gelenFiyat boşsa yedek olarak 100 TL alletnihaiFiyat= gelenFiyat ??100;console.log(nihaiFiyat);// Çıktı: 100
strictNullChecks
ve Kesin Olmayan İşaretçiler (!)strictNullChecks:
tsconfig.json
dosyasında bu ayar açıksa, TypeScript bir değişkenin null
olma ihtimalini asla göz ardı etmenize izin vermez. Sizi kontrol
yapmaya zorlar.
Kesin Olmayan İşaretçi
(Non-Null Assertion - !):
Yazılımcı olarak TypeScript'e "Söz veriyorum, bu
değer null veya undefined gelmeyecek, sen bana güven ve hata
vermeyi bırak" deme şeklidir. Dikkatli
kullanılmalıdır.
TypeScript'in gerçek gücü, kendi tiplerimizi yaratmamıza ve esnek yapılar kurmamıza izin vermesinden gelir.
any
Tipi ve noImplicitAny
Bayrağıany:
TypeScript'in koruma kalkanını tamamen kapatır. Değişkene her
şeyi yükleyebilirsiniz. TypeScript kullanırken any
kullanmak, dilin tüm faydalarını çöpe atmak demektir. Bu yüzden
asla kullanılmamalıdır.
noImplicitAny:
TypeScript'in, tipini tahmin edemediği değişkenleri gizlice any
yapmasını engeller. Sizi açıkça tip yazmaya zorlayan katı bir
güvenlik ayarıdır.
|)Bir değişkenin birden fazla farklı tipte değer alabilmesini sağlar.
// Müşteri kodu sayı da olabilir, metin de olabilirletmüşteriKodu:number|string;müşteriKodu =101;// DoğrumüşteriKodu ="M-202";// Doğru// müşteriKodu = true; // HATA! Birlik tiplerinde boolean yok.
Bir değişkenin sadece bizim belirlediğimiz spesifik kelimeleri veya sayıları alabilmesini sağlar.
lettrafikIşığı:"Kırmızı"|"Sarı"|"Yeşil";trafikIşığı ="Kırmızı";// Doğru// trafikIşığı = "Mavi"; // HATA! Sadece yukarıdaki üç kelimeden biri olabilir.
Kendi karmaşık veri modellerimizi oluşturmanın iki yolu vardır:
Type
Aliases (type):
Genelde birlik tipleri veya ilkel tipler için takma ad oluşturmakta
iyidir.
Interfaces (interface):
Sadece nesne yapılarını (object shapes) tanımlamak için
kullanılır. Genişletilmesi (extend edilmesi) çok kolaydır.
// Type Aliases ÖrneğitypeKimlikNo =string|number;// Interface Örneği (Nesneler için en iyisi)interfaceAraba{marka:string;modelYılı:number;}constbenimArabam: Araba = {marka:"Toyota",modelYılı:2022};
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.
letgelenVeri:any="Merhaba Dünya";// TypeScript'e bunun kesinlikle bir string olduğunu söylüyoruzletmetinUzunluğu= (gelenVeriasstring).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ü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.
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.
matematik.ts
(Dışarı aktaran dosya):
exportfunctiontopla(a:number,b:number):number{returna + b;}exportconstPI=3.14;
uygulama.ts
(İçeri aktaran dosya):
import{topla,PI}from"./matematik";console.log(topla(5,10));// Çıktı: 15console.log(PI);// Çıktı: 3.14
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.
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";as
İle İsim Değiştirmedefault
export: Bir dosyadan sadece tek bir
ana yapıyı dışarı aktarmak için kullanılır. Başına süslü
parantez koymadan direkt import edilir.
as
Sözdizimi: Import edilen bir yapının ismini o dosya içinde
değiştirmeye yarar.
// Varsayılan dışarı aktarma örneği// veriTabani.ts -> export default class VeriTabani {}importVeriTabanifrom"./veriTabani";// 'as' kullanarak isim değiştirmeimport{ toplaastoplamaIşlemi}from"./matematik";console.log(toplamaIşlemi(3,4));
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.
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.
Spread
(...):
Bir nesnenin veya dizinin elemanlarını tamamen açarak başka bir
yere kopyalamaya yarar.
Destructuring: Nesne veya dizi içindeki özellikleri tek hamlede değişkenlere atar.
// Destructuring ve Spread Örneğiconstkitap= {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)constgüncellenmişKitap= { ...kitap,fiyat:150};
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.
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.
Bir fonksiyonun kendisini de bir tip olarak tanımlayabilir ve başka yerlere parametre olarak güvenle aktarabiliriz.
// İki sayı alıp bir sayı döndüren fonksiyon tipi tanımladıktypeMatematikselIşlem = (x:number,y:number) =>number;constcarpmaIşlemi: MatematikselIşlem = (a,b) => a * b;constbolmeIşlemi: MatematikselIşlem = (a,b) => a / b;
Geldik dilin en can alıcı, mülakatlarda sıkça sorulan ve ileri düzey projelerin omurgasını oluşturan tiplerine.
Boyutu ve içindeki elemanlarının sırası, tipi kesin olarak belirlenmiş özel dizilerdir.
// İlk elemanı kesinlikle sayı, ikincisi kesinlikle metin olan bir Tupleletkoordinat: [number,string];koordinat = [34,"İstanbul"];// Doğru// koordinat = ["Ankara", 06]; // HATA! Sıralama yanlış.
unknown
Tipi vs any
Tipiany:
Her şeye izin verir, tipi kontrol etmez, güvensizdir.
unknown:
"Bu verinin ne olduğunu henüz bilmiyorum"
demektir. TypeScript, unknown
tipindeki bir veriyi siz Tip Daraltması (Type Narrowing)
yapana kadar kullanmanıza kesinlikle izin vermez. any
tipinden çok daha güvenlidir.
letgelenVeri:unknown="Merhaba";// console.log(gelenVeri.toUpperCase()); // HATA! TS der ki: "Dur önce emin ol!"if(typeofgelenVeri ==="string") {// Tip daraltıldı, artık güvenle string metotları kullanılabilirconsole.log(gelenVeri.toUpperCase());}
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.
interfaceKus{uc: () =>void}interfaceBalik{yuz: () =>void}// Tip Belirteci fonksiyonu (Type Predicate)functionkusMu(hayvan: Kus | Balik): hayvanisKus {return(hayvanasKus).uc !== undefined;}
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.
functionhataFırlat(mesaj:string):never{thrownewError(mesaj);// Fonksiyon hiçbir zaman bir şey döndürmez, çöker.}
&)Birden fazla arayüzü veya tipi birleştirerek yepyeni ve hepsinin özelliklerini içeren tek bir tip oluşturur.
interfaceCalisan{id:number;isim:string; }interfaceYonetici{departman:string; }// İkisini birleştirdiktypeMudur = Calisan & Yonetici;constyeniMudur: 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.
tsconfig.json)
tsconfig.json
dosyası, TypeScript derleyicisine (tsc)
kodlarımızı JavaScript'e çevirirken nasıl davranması
gerektiğini söyleyen ayar merkezidir.
target
ÖzelliğiYazdığımız modern TypeScript kodlarının, hangi JavaScript versiyonuna (örneğin ES5, ES6/ES2015, ES2022) dönüştürüleceğini belirler.
Örnek: Eğer eski
tarayıcıları desteklemeniz gerekiyorsa target:
"ES5" seçersiniz. Derleyici, modern
ok fonksiyonlarını (()
=> {}) herkesin anlayabileceği eski
function()
kalıplarına otomatik çevirir.
files,
include,
exclude)Projedeki her dosyanın derlenmesini istemeyebiliriz. Bunu yönetmek için üç ana ayar vardır:
files:
Sadece derlenmesini istediğiniz spesifik dosyaların listesini tek
tek buraya yazarsınız.
include:
Klasör bazlı kural koyar. Örneğin ["src/**/*"]
yazarak sadece src
klasörü altındaki tüm dosyaların derlenmesini sağlarsınız.
exclude:
Derleme dışı bırakılacak yerleri seçer. node_modules
veya test klasörleri (**/*.spec.ts)
buraya yazılarak derleyicinin boşuna yorulması engellenir.
outDir
ve rootDir)rootDir:
Orijinal TypeScript kodlarımızın bulunduğu ana klasörü
(genelde src)
işaret eder.
outDir:
Derleme bittikten sonra oluşacak tertemiz JavaScript dosyalarının
hangi klasöre (genelde dist
veya build)
kaydedileceğini belirler.
allowJs
ve checkJs
(JavaScript ile Ortak Çalışma)Eski bir JavaScript projesini yavaş yavaş TypeScript'e geçiriyorsanız bu ayarlar hayat kurtarır.
allowJs:
TypeScript projesinin içine düz .js
uzantılı dosyaları da dahil etmenize izin verir.
checkJs:
Düz JavaScript dosyalarının içinde bile TypeScript'in hataları
(açıklama satırlarındaki kurallara göre) denetlemesini sağlar.
TypeScript, JavaScript'in sınıflar (classes) yapısını çok daha güvenli ve güçlü bir hale getirir.
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.
classKurs{// Üye Değişkenler (Member Variables)başlık:string;fiyat:number;// Yapıcı Metotconstructor(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 üretmeconsttsKursu=newKurs("Sıfırdan İleri Seviye TypeScript",400);console.log(tsKursu.bilgiVer());
Sınıfın içindeki değişkenlere dışarıdan kimlerin ulaşıp ulaşamayacağını kontrol ederiz:
public
(Varsayılan): Her yerden erişilebilir.
private:
Sadece ve sadece o sınıfın kendi içinden erişilebilir.
Dışarıdan veya çocuğu olan sınıflardan erişilemez.
protected:
Sadece sınıfın içinden ve o sınıftan miras alan (türeyen)
çocuk sınıfların içinden erişilebilir.
readonly:
Değer sadece constructor içinde atanabilir, sonradan
değiştirilemez (Salt Okunur).
classBankaHesabı{publicmüşteriAdı:string;privatebakiye: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 metotparaYatır(miktar:number) {if(miktar >0)this.bakiye += miktar;}}
Bir private
değişkene erişirken veya onu değiştirirken araya bir kontrol
mekanizması (koruma duvarı) koymamızı sağlar.
classKullanıcı{private_yaş:number=0;// Getter (Yaşı okurken çalışır)getyaş():number{returnthis._yaş;}// Setter (Yaşı değiştirirken çalışır)setyaş(yeniYaş:number) {if(yeniYaş <0) {thrownewError("Yaş negatif olamaz!");}this._yaş = yeniYaş;}}constk=newKullanıcı();k.yaş =25;// Setter çalışırconsole.log(k.yaş);// Getter çalışır
extends
Anahtar KelimesiBir 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.
classPersonel{constructor(publicisim:string) {}selam Ver() {return`Merhaba, ben ${this.isim}`; }}// Personel sınıfından miras alıyoruzclassYazılımcıextendsPersonel {constructor(isim:string,publicuzmanlık:string) {super(isim);// Üst sınıfın constructor'ını çağırmak zorundayız!}}constdev=newYazılımcı("Hakan","TypeScript");console.log(dev.selamVer());// Miras alınan metot çalışır
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.
abstractclassVeriTabanıBağlantısı{abstractbağlan():void;// Çocuk sınıflar bu metodu zorunlu doldurmalı!kopart() {console.log("Bağlantı kesildi.");}}classMongoBağlantısıextendsVeriTabanı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.
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.
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.
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 belirttikfunctionilkElemanıGetir<T>(dizi: T[]): T {returndizi[0];}// Sayı dizisi gönderiyoruz, TS dönüş tipinin otomatik 'number' olduğunu biliyorletilkSayı= ilkElemanıGetir<number>([10,20,30]);// Metin dizisi gönderiyoruz, TS dönüş tipinin otomatik 'string' olduğunu biliyorletilkMetin= ilkElemanıGetir<string>(["Ahmet","Mehmet"]);
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.
interfaceUzunluğuOlan{length:number;}// T tipi, UzunluğuOlan arayüzünü karşılamak zorundadır!functionuzunlukYazdır<TextendsUzunluğ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!
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.
functionnesneÖzelliğiAl<T,KextendskeyofT>(nesne: T,anahtar: K) {returnnesne[anahtar];}constaraba= {marka:"Honda",model:"Civic",yıl:2023};letmarka= nesneÖzelliğiAl(araba,"marka");// Doğru// let renk = nesneÖzelliğiAl(araba, "renk"); // HATA! 'renk' araba nesnesinin anahtarlarından biri değil!
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).
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.
Bir fonksiyon her çalıştığında
konsola otomatik olarak bilgi yazdıran bir @Logla
dekoratörü tasarlayalım:
functionLogla(target:any,propertyKey:string,descriptor: PropertyDescriptor) {constorijinalMetot= descriptor.value;// Orijinal fonksiyonun davranışını değiştiriyoruzdescriptor.value =function(...args:any[]) {console.log(`[LOG]: ${propertyKey} metodu çalıştırıldı. Parametreler: ${JSON.stringify(args)}`);returnorijinalMetot.apply(this, args);// Orijinal fonksiyonu çalıştır};}classHesapMakinesi{@Logla// Dekoratörü metodun başına taktık!toplama(a:number,b:number) {returna + b;}}consthesap=newHesapMakinesi();hesap.toplama(5,8);// Konsol Çıktısı: [LOG]: toplama metodu çalıştırıldı. Parametreler: [5,8]
Sınıf Dekoratörleri: Doğrudan sınıf tanımının üzerine yazılır ve sınıfın yapısını tamamen değiştirebilir veya yeni özellikler dondurabilir.
Özellik Dekoratörleri: Sınıf içindeki değişkenlerin (property) başlangıç değerlerini kontrol etmek veya değiştirmek için kullanılır.
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.
Gerçek dünya projelerinde sistem sıfırdan adım adım kurulur. Biz de projemizi ilk komutla ayağa kaldırıyoruz.
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 -ynpm install expressnpm install -D typescript @types/node @types/express ts-node nodemon
Projemizin ana klasöründe
(src/server.ts)
ilk sunucumuzu tamamen TypeScript kurallarına uygun olarak ayağa
kaldırıyoruz.
importexpress, {Request,Response}from'express';constapp= express();constPORT=3000;// JSON formatındaki verileri okuyabilmek için gömülü parser'ı açıyoruzapp.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.`);});
dotenv)Hot-Reload:
Kodda bir değişiklik yaptığımızda sunucunun otomatik olarak
kapanıp açılmasını sağlar. Bunun için nodemon
ve ts-node
ikilisini package.json
içine bir script olarak yazarız.
dotenv:
Port numarası, veri tabanı şifreleri gibi gizli bilgileri kodun
içine açıkça yazmak yerine .env
dosyasında saklamamızı sağlar.
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.
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.
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 olacakexportclassCourse{@PrimaryGeneratedColumn()// Otomatik artan benzersiz IDid:number;@Column()// Düz metin kolonutitle: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[];}
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.
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.
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)constcourseRepository= appDataSource.getRepository(Course);constcourses=awaitcourseRepository.find();res.json(courses);});
PATCH: Bir nesnenin tüm özelliklerini değil, sadece gelen birkaç özelliğini değiştirmek için kullanılır.
DELETE: Belirtilen ID'ye sahip veriyi kalıcı olarak veri tabanından siler.
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.
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.
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.
Kullanıcı kayıt olurken şifresini güvenli bir şekilde hash'leyen TypeScript fonksiyonunu yazalım:
importcryptofrom'crypto';exportfunctionsifreHashle(acikSifre:string):string{// Rastgele bir tuz (salt) oluşturabilir veya sabit güvenli bir algoritma kullanabilirizconsthash= crypto.createHash('sha256');// SHA-256 algoritmasıhash.update(acikSifre);returnhash.digest('hex');// Geriye kırılması imkansız 64 karakterli bir metin döner}
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ı TablosuexportconstkullaniciKayit=async(req: Request,res: Response) => {const{,password,name} = req.body;constuserRepository= appDataSource.getRepository(User);// Şifreyi hash'liyoruzconstguvenliSifre= sifreHashle(password);constyeniKullanici= userRepository.create({email,password: guvenliSifre,name,role:"USER"// Varsayılan olarak düz üye rolü veriyoruz});awaituserRepository.save(yeniKullanici);res.status(201).json({mesaj:"Kullanıcı başarıyla oluşturuldu."});};
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.
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.
importjwtfrom'jsonwebtoken';constJWT_SECRET_KEY="SÜPER_GİZLİ_ANAHTAR_KODUMUZ";exportfunctiontokenOlustur(kullaniciId:number,rol:string):string{// Kullanıcının bilgilerini token içine güvenle gömüyoruzreturnjwt.sign({id: kullaniciId,role: rol },JWT_SECRET_KEY,{expiresIn:'1h'}// Token 1 saat sonra otomatik olarak iptal olur);}
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.
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.
Gelen isteklerin başlığındaki (Header) token'ı kontrol eden ve geçerliyse isteğin devam etmesine izin veren koruma kapısıdır.
import{Request,Response,NextFunction}from'express';importjwtfrom'jsonwebtoken';// Express Request tipini genişleterek içine kullanıcı bilgisini ekleyebiliyoruzexportinterfaceKimlikliIstekextendsRequest {kullanici?:any;}exportfunctionkimlikDogrula(req: KimlikliIstek,res: Response,next: NextFunction) {constauthHeader= req.headers['authorization'];consttoken= authHeader && authHeader.split(' ')[1];// Bearer TOKEN_KODUif(!token) {returnres.status(401).json({hata:"Erişim engellendi. Token bulunamadı!"});}try{constdogrulanmisVeri= jwt.verify(token,"SÜPER_GİZLİ_ANAHTAR_KODUMUZ");req.kullanici = dogrulanmisVeri;// Kullanıcı bilgilerini isteğe eklediknext();// Her şey yolunda, bir sonraki fonksiyona geçebilirsin!}catch(hata) {res.status(403).json({hata:"Geçersiz veya süresi dolmuş token!"});}}
Kimliği doğrulanan kullanıcının,
o işlemi yapmaya yetkisinin olup olmadığını denetleriz. Örneğin
bir kursu sadece ADMIN
silebilir.
exportfunctionrolYetkisiVer(gerekliRol:string) {return(req: KimlikliIstek,res: Response,next: NextFunction) => {if(!req.kullanici || req.kullanici.role !== gerekliRol) {returnres.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.
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.tsimportrequestfrom'supertest';importexpressfrom'express';import{kimlikDogrula,rolYetkisiVer,KimlikliIstek}from'../src/authMiddleware';import{tokenOlustur}from'../src/jwtUreteci';constapp= 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() => {constresponse=awaitrequest(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() => {constresponse=awaitrequest(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 üretiyoruzconstuserToken= tokenOlustur(99,"USER");constresponse=awaitrequest(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 üretiyoruzconstadminToken= tokenOlustur(1,"ADMIN");constresponse=awaitrequest(app).delete("/api/test-kurs/1").set("Authorization",`Bearer ${adminToken}`);expect(response.status).toBe(200);expect(response.body.mesaj).toBe("Kurs silindi");});});
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?
[]
let
isActive: boolean = "true";
Yanlış.
"true"
bir string (metin) değeridir, boolean değildir.
let
isActive: boolean = 1;
Yanlış.
1
sayısal (number) bir değerdir, strict mode altında boolean
değişkenine atanamaz.
let
isActive: boolean = false;
Doğru!
false
değeri tam olarak boolean
ilkel tipine karşılık gelir.
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;
[]
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.
Açık tip belirtimi (Explicit Annotation) zorunludur.
Yanlış. Değer ataması yapılan durumlarda tip belirtmek zorunlu değildir.
Değişkenin
türü varsayılan olarak any
olur.
Yanlış.
İlk atanan değere (95)
dayanarak türü number
olarak belirlenir, any
olmaz.
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;
[]
string
türü asla metin kabul etmez.
Yanlış.
string
türü metin değerlerini kabul eder.
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.
Değişken
const
ile tanımlanmalıdır.
Yanlış.
Hatanın sebebi let
veya const
kullanımı değil, null
atamasıdır.
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?
[]
İ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.
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.
unknown
türü sadece sayılar için kullanılır.
Yanlış.
unknown
herhangi bir değer alabilir, sayıya özel değildir.
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?
[]
İ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.
İkinci
eleman number
türünde olmalıdır.
Yanlış.
Tanıma göre ikinci eleman number
olmalıdır, bu ifade doğrudur.
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.
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 }
[]
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.
Hepsine rastgele sayılar atar.
Yanlış. Atamalar rastgele değil, sıralı ve deterministiktir.
Hepsine boş string atar.
Yanlış. Sayısal enum'lar sayısal değerler barındırır.
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?
[]
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.
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.
Type alias'lar nesne tanımlayamaz.
Yanlış. Type alias'lar da nesne türlerini tanımlayabilir.
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?
[]
private
Yanlış.
private
üyeler sadece tanımlandığı sınıf içinden erişilebilirdir,
varsayılan değildir.
protected
Yanlış.
protected
alt sınıflardan erişimi ifade eder, varsayılan değildir.
public
Doğru!
Herhangi bir erişim belirteci yazılmadığında üyeler varsayılan
olarak public
kabul edilir.
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?
[]
Sadece sınıfın dışından ve türetilmiş sınıflardan.
Yanlış.
Sınıfın dışından protected
üyeye erişilemez.
Herhangi bir yerden kısıtlama olmaksızın.
Yanlış.
Kısıtlamasız olan public
türüdür.
Sadece aynı dosyadaki bağımsız fonksiyonlardan.
Yanlış.
Sınıf dışındaki bağımsız fonksiyonlar protected
üyeleri göremez.
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?
[]
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.
İçlerinde gövdeli normal metotlar bulunamaz.
Yanlış. Soyut sınıflar hem gövdesiz soyut metotlar hem de normal gövdeli metotlar içerebilir.
Tüm
metotları mutlaka abstract
olmak zorundadır.
Yanlış. İstendiği kadar normal metot eklenebilir, zorunlu değildir.
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?
[]
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.
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.
Geri dönüş değerinin uzunluğunu 0 yapar.
Yanlış. Geri dönüş değeri üzerinde bir uzunluk ataması yapmaz.
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?
[]
Sadece 1 parametre (değer)
Yanlış. Tek parametre almaz.
4 parametre
Yanlış. Standart metot dekoratörleri 3 adet parametre alır.
3
parametre: (target,
propertyKey, descriptor)
Doğru! Sırasıyla prototype/constructor, metot adı ve property descriptor parametrelerini alır.
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?
[]
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.
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.
Sadece hata ayıklama modu ve kaynak haritaları (source map) oluşturur.
Yanlış. Bu ayarlar hata ayıklama moduyla doğrudan ilgili değildir.
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?
[]
instanceof
Yanlış.
instanceof
sınıflar (classes) için kullanılır, ilkel (primitive) tiplerde
string
veya number
ayırt etmek için yetersizdir.
checkType
Yanlış.
checkType
diye bir JavaScript/TypeScript operatörü yoktur.
is
Yanlış.
is
özel tip koruma fonksiyonlarında (type predicate) kullanılır,
temel operatör değildir.
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?
[]
Required<User>
Yanlış.
Required
tüm alanları zorunlu hale getirir, opsiyonel yapmaz.
Partial<User>
Doğru!
Partial<T>,
T
türündeki tüm özellikleri isteğe bağlı (optional) yapar.
Readonly<User>
Yanlış.
Readonly
alanları sadece okunabilir (değiştirilemez) yapar.
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ı
[]
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.
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.
interfaceBasariliOdeme{durum:"basarili";// Discriminated alan (Ayırt edici literal)islemId:string;tutar:number;}interfaceHataliOdeme{durum:"hatali";hataKodu:number;mesaj:string;}typeOdemeSonucu = BasariliOdeme | HataliOdeme;functionodemeMesajiniYazdir(sonuc: OdemeSonucu):string{// TypeScript 'durum' alanına bakarak if/switch içinde tipleri otomatik daraltırswitch(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;}}
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.
interfaceIdZorunlulugu{id:number;}// T tipi kesinlikle içinde 'id' barındıran bir nesne olmak zorundaclassVeriDeposu<TextendsIdZorunlulugu> {privateveriler: T[] = [];kaydet(yeniVeri: T):void{this.veriler.push(yeniVeri);console.log("Veri depoya eklendi.");}idIleBul(id:number): T |undefined{returnthis.veriler.find(item => item.id === id);}hepsiniGetir(): T[] {returnthis.veriler;}}// Kullanım örneği:interfaceUrunextendsIdZorunlulugu {ad:string;fiyat:number;}consturunDeposu=newVeriDeposu<Urun>();urunDeposu.kaydet({id:101,ad:"Laptop",fiyat:45000});console.log(urunDeposu.idIleBul(101)?.ad);// Çıktı: Laptop
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ıfunctionYetkiKontrolu(gerekenRol:string) {returnfunction(target:any,propertyKey:string,descriptor: PropertyDescriptor) {constorijinalMetot= descriptor.value;descriptor.value =function(...args:any[]) {// Sistemde aktif olan kullanıcının rolünü simüle ediyoruzconstaktifKullanici= {isim:"Mert",rol:"USER"};if(aktifKullanici.rol !== gerekenRol) {thrownewError(`🚫 GÜVENLİK UYARISI: ${propertyKey} fonksiyonunu tetiklemek için ${gerekenRol} yetkiniz yok!`);}// Yetki varsa orijinal metot çalışırreturnorijinalMetot.apply(this, args);};};}classAdminPaneli{@YetkiKontrolu("ADMIN")// Sadece ADMIN rolü bu metodu çalıştırabilirsistemiKapat() {console.log("Sistem başarıyla kapatılıyor...");}}try{constpanel=newAdminPaneli();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.
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.
Diyelim ki bir e-ticaret sisteminde kullanıcı rollerini yönetiyoruz.
// 1. Aşama: Rollerimizi TanımlıyoruzinterfaceAdminKullanici{role:"admin";sistemiKapat: () =>void; }interfaceEditorKullanici{role:"editor";makaleDuzenle: () =>void; }interfaceUyeKullanici{role:"uye";yorumYap: () =>void; }// Kullanıcı birlik (union) türütypeKullaniciTipi = AdminKullanici | EditorKullanici | UyeKullanici;functionyetkiKontrolMerkezi(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;}}
Aylar
sonra projeye yeni bir özellik geldi ve KullaniciTipi
içerisine KuryeKullanici
adında 4. bir rol eklendi:
interfaceKuryeKullanici{role:"kurye";paketTeslimEt: () =>void; }// Yeni rolü birliğe ekliyoruztypeKullaniciTipi = 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!"
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.
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}}}
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";exportclassUserService{kurslariListele() {console.log("Kısayol mimarisi başarıyla çalışıyor!");}}
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:
Paketi
kurun: npm
install -D tsconfig-paths
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.
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"}}
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ıconstTsconfigPathsPlugin= require('tsconfig-paths-webpack-plugin');constpath= 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: [newTsconfigPathsPlugin({configFile: path.resolve(__dirname,'./tsconfig.json')})]},output: {filename:'bundle.js',path: path.resolve(__dirname,'dist'),},};
@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.
src/├── config/│ └── database.ts├── models/│ ├── Course.ts│ └── Lesson.ts└── server.ts
@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ıyoruzimport{Course}from"@modeller/Course";@Entity({name:"LESSONS"})exportclassLesson{@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 temizlendiimport{Lesson}from"@modeller/Lesson";@Entity({name:"COURSES"})exportclassCourse{@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[];}
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.tsimport{DataSource}from"typeorm";// 🎯 REFACTOR: Tüm modeller merkezi takma ad yapısından çekiliyorimport{Course}from"@modeller/Course";import{Lesson}from"@modeller/Lesson";exportconstappDataSource=newDataSource({type:"postgres",host:"localhost",port:5432,username:"postgres",password:"secret_password",database:"bootcamp_db",synchronize:true,// Geliştirme aşamasında tabloları otomatik üretirlogging:false,entities: [Course, Lesson],// Temizlenmiş modeller listesi});
src/server.tsimportexpress, {Request,Response}from"express";import{appDataSource}from"@config/database";import{Course}from"@modeller/Course";constapp= express();app.use(express.json());app.get("/api/courses",async(req: Request,res: Response) => {try{constrepo= appDataSource.getRepository(Course);consttumKurslar=awaitrepo.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şlatappDataSource.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.
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.
Terminale aşağıdaki komutu yazarak gerekli ESLint eklentilerini kurun:
npm install -D eslint-plugin-import eslint-import-resolver-typescripteslint.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}}]}}
.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}
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.
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.tsimport{appDataSource}from"@config/database";import{Course}from"@modeller/Course";exportclassCourseService{asyncgelismisKursRaporu(aramaKelimesi:string,minimumDersSayisi:number) {// 'course' takma adıyla (alias) sorguyu başlatıyoruzconstsorguOlusturucu= appDataSource.getRepository(Course).createQueryBuilder("course");constrapor=awaitsorguOlusturucu/* 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önerreturnrapor;}asyncsadeceIstatistikGetir() {/* SQL ham aggregate (COUNT, SUM) fonksiyonlarını kullanma yöntemi */constistatistik=awaitappDataSource.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ürreturnistatistik;}}
SQL
Injection Koruması: .where("...
:kelime", { kelime }) yapısı, dışarıdan
gelebilecek zararlı SQL komutlarını otomatik olarak temizler
(sanitize eder).
Bellek
(RAM) Yönetimi: .select([])
fonksiyonu sayesinde veri tabanından sadece o an ekranda
göstereceğimiz 3-4 kolonu çekerek sunucuyu boşuna yormamış
oluruz.
İlişkisel Veri Kontrolü:
.leftJoinAndSelect
komutu, TypeScript katmanında Course
nesnesinin altındaki lessons:
Lesson[] dizisini tek bir verimli SQL
sorgusuyla (N+1 problemini engelleyerek) doldurur.
****************************************************************************