Modern web uygulamaları geliştirmek isteyenler için Express ve TypeScript ikilisi, güçlü ve ölçeklenebilir backend çözümleri sunar. Bu adım adım rehber, tip güvenli bir Express sunucusunu sıfırdan nasıl kuracağınızı, temelden ileri düzeye kadar tüm detaylarıyla öğretecek. Hataları minimuma indirirken geliştirme sürecinizi hızlandırmanın yollarını keşfedin.
Giriş: Neden Express ve TypeScript İkilisini Tercih Etmelisiniz?
Günümüzün rekabetçi yazılım dünyasında, hızlı, güvenilir ve sürdürülebilir uygulamalar geliştirmek, projenizin başarısı için kritik öneme sahiptir. JavaScript, web geliştirmede tartışmasız bir hakimiyete sahip olsa da, büyük ölçekli projelerde veya kalabalık ekiplerle çalışırken bazı zorluklar ortaya çıkarabilir. Dinamik tip yapısı, beklenmedik hatalara ve kodun okunabilirliğinin azalmasına neden olabilir. İşte tam bu noktada TypeScript ve Express kombinasyonu devreye girer ve geliştiricilere adeta bir nefes aldırır.
Express.js, Node.js ekosisteminin en popüler ve minimalist web çatılarından biridir. Hızla API’ler ve web uygulamaları oluşturmak için esnek bir temel sunar. Geniş middleware desteği ve basit yönlendirme mekanizmaları sayesinde geliştiricilerin karmaşık sorunlara odaklanmasını kolaylaştırır. Ancak Express, varsayılan olarak JavaScript ile çalıştığı için yukarıda bahsedilen tip güvenliği eksikliğini taşır. Bu da özellikle büyüyen projelerde, fonksiyonların beklediği parametre tipleri, dönen değerler veya nesne yapıları konusunda belirsizliklere yol açabilir, bu da hataların fark edilmesini zorlaştırır ve hata ayıklama sürecini uzatır.
TypeScript ise, Microsoft tarafından geliştirilen ve JavaScript’in tüm özelliklerini barındıran, üzerine statik tip denetimi ekleyen bir süper kümesidir. Yani, yazdığınız TypeScript kodu en nihayetinde tarayıcıların veya Node.js’in anlayacağı düz JavaScript koduna derlenir. Ancak bu derleme öncesinde, TypeScript derleyicisi kodunuzdaki potansiyel tip hatalarını yakalar. Bu sayede, uygulamanızı daha çalıştırmadan birçok hatayı tespit edebilir ve giderebilirsiniz. Tip güvenliği, özellikle büyük projelerde ekip üyelerinin birbirlerinin kodunu daha kolay anlamasına, entegrasyon hatalarının azalmasına ve refaktoring işlemlerinin daha güvenli yapılmasına olanak tanır. Bir metodun hangi tipte argümanlar aldığını veya hangi tipte bir değer döndürdüğünü açıkça belirtmek, kodun adeta kendi kendini dokümante etmesini sağlar.
Express ve TypeScript’i bir araya getirmek, her iki dünyanın en iyi özelliklerini kullanmak anlamına gelir: Express’in hafifliği ve esnekliği ile TypeScript’in tip güvenliği ve geliştirici araçları desteği. Bu kombinasyon, özellikle kurumsal düzeydeki projelerde, API geliştirme süreçlerinde ve mikroservis mimarilerinde sıkça tercih edilir. Geliştirici verimliliğini artırır, kod kalitesini yükseltir ve uygulamalarınızın daha ölçeklenebilir olmasını sağlar. Ayrıca, VS Code gibi modern IDE’ler, TypeScript ile mükemmel bir entegrasyon sunarak akıllı kod tamamlama, anında hata tespiti ve kolay navigasyon gibi özelliklerle geliştirme deneyimini üst seviyeye taşır.
Vaka Analizi: Büyük Bir E-ticaret Platformunun TypeScript’e Geçişi
Yakın zamanda X e-ticaret platformu, büyüyen kod tabanı ve genişleyen geliştirici ekibiyle birlikte, JavaScript tabanlı backend’inde artan hata oranları ve yavaşlayan geliştirme süreçleriyle mücadele ediyordu. Özellikle farklı modüller arasındaki entegrasyonlarda ve yeni özellik geliştirmelerinde, tip uyuşmazlıklarından kaynaklanan hatalar sıkça yaşanıyordu. Platformun mühendislik liderliği, bu sorunları aşmak ve kod kalitesini artırmak amacıyla backend’lerini aşamalı olarak TypeScript’e taşıma kararı aldı. Geçiş sürecinde, mevcut JavaScript kodları parça parça TypeScript’e dönüştürüldü ve yeni geliştirilen tüm servisler doğrudan TypeScript ile yazıldı. Bu strateji sayesinde, geçişin ilk aylarında bile hata raporlarında %30’luk bir düşüş gözlemlendi. Ayrıca, tip denetiminin sağladığı otokomple ve IntelliSense özellikleri sayesinde, geliştiricilerin yeni modüllere adaptasyon süresi kısaldı ve kod yazma hızı %15 arttı. Projenin genel sürdürülebilirliği ve ölçeklenebilirliği önemli ölçüde iyileşirken, ekip üyeleri arasındaki iş birliği de daha sorunsuz hale geldi. Bu vaka, TypeScript’in büyük ve karmaşık sistemlerde nasıl bir değer yarattığının somut bir örneğidir.
Temel Kavramlar: Express, TypeScript ve Node.js Nedir?
Bir Express TypeScript sunucusu kurmaya başlamadan önce, bu ekosistemi oluşturan temel taşları anlamak önemlidir. Bu üç ana bileşen, modern backend geliştirmenin yapı taşlarını oluşturur ve her birinin kendine özgü bir rolü vardır. Node.js, JavaScript’i sunucu tarafında çalıştırmamızı sağlayan bir çalışma zamanı ortamıdır. Google Chrome’un V8 JavaScript motoru üzerine inşa edilmiştir ve bu sayede JavaScript’in tarayıcı dışındaki ortamlarda da çalışabilmesine imkan tanır. Node.js, özellikle I/O yoğun uygulamalar için tasarlanmıştır; yani dosya işlemleri, veritabanı bağlantıları veya ağ istekleri gibi işlemler sırasında bloklama yapmadan çalışabilir. Bu, onu gerçek zamanlı uygulamalar, sohbet sunucuları veya API’ler için ideal bir seçim haline getirir. Node.js’in asenkron ve olay tabanlı mimarisi, yüksek performanslı ve ölçeklenebilir sunucular oluşturmanın anahtarıdır.
Express.js ise Node.js için yazılmış, hafif ve esnek bir web uygulama çatısıdır. Node.js’in sunduğu düşük seviyeli HTTP modülünün karmaşıklığını soyutlayarak, web uygulaması veya API geliştirmeyi çok daha kolay hale getirir. Yönlendirme (routing), middleware desteği, şablon motoru entegrasyonu gibi temel özellikleri sunar. “Minimalist” olarak tanımlanmasının sebebi, sadece temel özelliklere odaklanması ve geliştiricilere diledikleri ek modülleri (veritabanı sürücüleri, kimlik doğrulama kütüphaneleri vb.) entegre etme özgürlüğü sunmasıdır. Bu esneklik, Express’i farklı proje ihtiyaçlarına göre kolayca uyarlanabilir kılar. Çoğu RESTful API ve mikroservis mimarisi, temelini Express.js üzerine kurar çünkü hızlı prototipleme ve üretim ortamında güvenilir performans sunar.
TypeScript ise, JavaScript’in tip eklenmiş bir süper kümesidir. Bu ne anlama geliyor? Temelde, yazdığınız tüm geçerli JavaScript kodları aynı zamanda geçerli TypeScript kodlarıdır. Ancak TypeScript, JavaScript’e isteğe bağlı statik tipler (örneğin string, number, boolean, interface, type) eklemenize olanak tanır. Bu tipler, kodunuzu yazarken değişkenlerin, fonksiyon parametrelerinin ve dönüş değerlerinin türünü belirtmenizi sağlar. TypeScript derleyicisi (tsc), kodunuzu derlemeden önce bu tipleri kontrol eder ve bir hata durumunda sizi uyarır. Bu kontrol, runtime (çalışma zamanı) hatalarını compile-time (derleme zamanı) hatalarına dönüştürerek, uygulamaların daha kararlı olmasına yardımcı olur. Ayrıca, modern IDE’ler (örneğin VS Code) TypeScript ile derinlemesine entegre olarak, kod tamamlama, hata analizi, yeniden düzenleme (refactoring) ve kod üzerinde gezinme gibi özellikleri önemli ölçüde geliştirir. Bu da özellikle büyük ve karmaşık kod tabanlarında geliştirici verimliliğini artırır.
Bu üçlünün uyumu ise şöyledir: Node.js, JavaScript kodunuzu sunucuda çalıştırmak için ortamı sağlar. Express.js, bu ortam üzerinde web uygulamaları ve API’ler oluşturmak için basit ve güçlü bir çerçeve sunar. TypeScript ise, Express uygulamalarınızı geliştirirken tip güvenliği ve gelişmiş geliştirici deneyimi ekleyerek, hem kod kalitesini hem de bakım kolaylığını artırır. Geliştirme sırasında TypeScript’in statik tip denetimi sayesinde daha az hata yaparken, üretim ortamına derlenmiş saf JavaScript kodunuz Node.js üzerinde yüksek performansla çalışır. Bu sinerji, modern, güvenilir ve sürdürülebilir backend sistemleri oluşturmak için ideal bir kombinasyon sunar.
Adım Adım Kurulum: Express TypeScript Projesi Nasıl Başlatılır?
Artık temel kavramlara aşina olduğunuza göre, kendi Express TypeScript sunucumuzu kurmaya başlayabiliriz. Bu bölümde, projenizi sıfırdan başlatmak için gerekli tüm adımları detaylı bir şekilde ele alacağız. Başlamadan önce bilgisayarınızda Node.js ve npm (Node Package Manager) veya Yarn kurulu olduğundan emin olun. Genellikle Node.js kurulumu ile npm de otomatik olarak gelir.
-
Yeni Bir Proje Dizini Oluşturma:
Öncelikle projeniz için temiz bir klasör oluşturalım ve içine girelim. Bu klasör, tüm kod dosyalarınızı ve bağımlılıklarınızı barındıracak.
mkdir express-ts-server cd express-ts-server -
package.jsonDosyasını Başlatma:Projemizin bağımlılıklarını ve betiklerini yönetmek için bir
package.jsondosyasına ihtiyacımız var. Bu komut, size birkaç soru soracak ve ardından dosyayı oluşturacaktır. Varsayılan değerleri kabul edebilirsiniz.npm init -y # veya yarn kullanıyorsanız: # yarn init -y-ybayrağı, tüm sorulara varsayılan yanıtlar vermenizi sağlar. -
Express ve TypeScript Bağımlılıklarını Ekleme:
Şimdi projemizin ana bağımlılıkları olan Express'i ve TypeScript'i kurmalıyız. Ayrıca, Express için TypeScript tip tanımlarına da ihtiyacımız var. Tip tanımları, TypeScript'in Express kütüphanesini doğru şekilde anlamasını ve tip denetimi yapmasını sağlar. Bunlar genellikle
@types/önekiyle gelir ve geliştirme bağımlılığı olarak kurulur.npm install express npm install --save-dev typescript @types/express @types/node # veya yarn kullanıyorsanız: # yarn add express # yarn add -D typescript @types/express @types/nodeBurada
expressçalışma zamanı (runtime) bağımlılığıdır.typescript,@types/expressve@types/nodeise sadece geliştirme sırasında ihtiyaç duyulandevDependencies'tir. -
TypeScript Yapılandırması (
tsconfig.json):TypeScript derleyicisinin projemizi nasıl derleyeceğini belirtmek için bir
tsconfig.jsondosyasına ihtiyacımız var. Bu dosya, TypeScript'in derleme seçeneklerini içerir (örneğin, hedef JavaScript sürümü, modül sistemi, çıktı dizini vb.).npx tsc --initBu komut, geçerli dizinde temel bir
tsconfig.jsondosyası oluşturacaktır. Oluşturulan dosyayı açıp bazı önemli ayarları düzenlememiz gerekiyor:{ "compilerOptions": { "target": "es6", /* Hedef JavaScript sürümü */ "module": "commonjs", /* Modül sistemi */ "outDir": "./dist", /* Derlenmiş JS dosyalarının çıktı dizini */ "rootDir": "./src", /* TypeScript kaynak dosyalarının kök dizini */ "strict": true, /* Tüm katı tip denetim seçeneklerini etkinleştir */ "esModuleInterop": true, /* Varsayılan importları düzgün çalıştır */ "skipLibCheck": true, /* Kütüphanelerin tip kontrolünü atla */ "forceConsistentCasingInFileNames": true /* Dosya adlarında tutarlı büyük/küçük harf kullanımını zorunlu kıl */ }, "include": ["src/**/*.ts"], /* Derlenecek TypeScript dosyalarını belirtir */ "exclude": ["node_modules"] /* Derleme dışında bırakılacak dosyaları belirtir */ }Yukarıdaki ayarlar, modern bir Node.js uygulamasında en iyi uygulamaları yansıtır.
target: "es6"modern JavaScript özelliklerini kullanmamızı sağlarken,outDir: "./dist"derlenmiş kodumuzundistklasörüne gitmesini sağlar.rootDir: "./src"ise TypeScript kaynak kodumuzunsrcklasöründe olduğunu belirtir. -
Geliştirme Bağımlılıklarını Ekleme (
nodemon,ts-node):Geliştirme sürecini kolaylaştırmak için iki ek araca ihtiyacımız olacak:
nodemon: Kaynak kodunuzda değişiklik yaptığınızda Node.js uygulamasını otomatik olarak yeniden başlatır. Bu, manuel yeniden başlatma zahmetini ortadan kaldırır.ts-node: TypeScript dosyalarını doğrudan Node.js ile çalıştırmanızı sağlar, böylece her değişiklikte manuel derleme yapmanıza gerek kalmaz. Bu, geliştirme döngüsünü büyük ölçüde hızlandırır.
npm install --save-dev nodemon ts-node # veya yarn kullanıyorsanız: # yarn add -D nodemon ts-node -
package.jsonİçindeki Betikleri Güncelleme:Şimdi
package.jsondosyamıza bazı betikler (scripts) ekleyerek, sunucumuzu kolayca başlatabilir ve derleyebiliriz.startbetiği uygulamanın üretimde nasıl çalışacağını,devbetiği ise geliştirme sırasında nasıl çalışacağını tanımlar.{ "name": "express-ts-server", "version": "1.0.0", "description": "", "main": "dist/index.js", "scripts": { "build": "npx tsc", "dev": "nodemon --exec ts-node src/index.ts", "start": "node dist/index.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.18.2" }, "devDependencies": { "@types/express": "^4.17.17", "@types/node": "^20.5.9", "nodemon": "^3.0.1", "ts-node": "^10.9.1", "typescript": "^5.2.2" } }Artık temel kurulum tamamlandı!
srcdizinimizi oluşturup ilk Express TypeScript uygulamasını yazmaya başlayabiliriz.
Uzman İpucu: Otomatik Yeniden Yükleme ile Geliştirme Akışınızı Hızlandırın
Geliştirme sürecinde her kod değişikliğinden sonra sunucuyu manuel olarak durdurup başlatmak zaman kaybıdır. nodemon ve ts-node ikilisi sayesinde, package.json dosyasındaki "dev": "nodemon --exec ts-node src/index.ts" betiği, src klasöründeki TypeScript dosyalarınızda bir değişiklik algıladığında uygulamanızı otomatik olarak yeniden derleyip başlatır. Bu sayede tarayıcınızda anında güncellemeleri görebilir, geliştirme akışınızı kesintisiz sürdürebilirsiniz. Büyük projelerde bu küçük otomasyon, geliştirici verimliliğini %20-30 oranında artırabilir.
Mobil Uyumlu HTML için Bir Örnek (İlgili CSS ile):
Web siteleri ve uygulamalar günümüzde farklı ekran boyutlarına uyum sağlamalıdır. İşte basit bir media query örneği:
/* Genel stiller */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
}
.container {
width: 90%;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
/* Küçük ekranlar için (örneğin mobil cihazlar) */
@media (max-width: 768px) {
.container {
width: 95%;
padding: 15px;
}
h2 {
font-size: 1.5em;
}
p {
font-size: 0.9em;
}
}
/* Daha küçük ekranlar için */
@media (max-width: 480px) {
body {
padding: 10px;
}
.container {
width: 100%;
padding: 10px;
border-radius: 0;
}
h2 {
font-size: 1.2em;
}
}
Bu CSS kodları, farklı ekran boyutlarına göre .container elementinin genişliğini ve iç dolgusunu ayarlayarak mobil uyumluluk sağlar. Bu, kullanıcı deneyimini önemli ölçüde iyileştirir.
İlk TypeScript Express Uygulamanızı Oluşturma
Şimdi projemizi canlıya geçirecek olan kod kısmına geçelim. src dizinini oluşturalım ve içerisine index.ts dosyasını ekleyelim.
mkdir src
touch src/index.ts
Ardından src/index.ts dosyasını açın ve aşağıdaki temel Express sunucu kodunu yapıştırın:
import express, { Request, Response } from 'express';
const app = express();
const port = process.env.PORT || 3000;
// JSON body parsing middleware
app.use(express.json());
// Ana sayfa rotası
app.get('/', (req: Request, res: Response) => {
res.send('Merhaba Dünya! Express TypeScript Sunucusu Çalışıyor!');
});
// Örnek bir API rotası
app.get('/api/users', (req: Request, res: Response) => {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
res.json(users);
});
// Sunucuyu belirtilen portta başlat
app.listen(port, () => {
console.log(Sunucu http://localhost:${port} adresinde çalışıyor.);
});
Bu kod bloğu, temel bir Express sunucusunun nasıl yapılandırıldığını gösterir:
import express, { Request, Response } from 'express';satırı, Express kütüphanesini ve tip tanımlarını içeri aktarır.RequestveResponsetipleri, gelen istekleri ve gönderilecek yanıtları tip güvenli bir şekilde yönetmemizi sağlar.const app = express();ile bir Express uygulaması örneği oluşturulur.const port = process.env.PORT || 3000;sunucunun dinleyeceği portu belirler. Ortam değişkenlerindenPORTdeğerini almazsa, varsayılan olarak 3000 portunu kullanır. Bu, üretim ortamında farklı bir port kullanmak istediğinizde esneklik sağlar.app.use(express.json());bir middleware'dir. Gelen HTTP isteklerinin gövdesindeki JSON verilerini ayrıştırmak için kullanılır. Bu sayedereq.body'ye erişebilirsiniz.app.get('/', ...)veapp.get('/api/users', ...)gibi satırlar, farklı URL yolları için HTTP GET isteklerini nasıl yöneteceğimizi tanımlayan rota işleyicileridir. Ana sayfaya yapılan istekler "Merhaba Dünya!" metniyle yanıtlanırken,/api/usersrotasına yapılan istekler bir kullanıcı listesi döndürür.app.listen(port, () => {...});sunucuyu başlatır ve belirtilen portu dinlemesini sağlar. Sunucu başarıyla başladığında bir konsol mesajı gösterilir.
Şimdi projenizi çalıştırmak için terminalinizde aşağıdaki komutu kullanın:
npm run dev
# veya yarn kullanıyorsanız:
# yarn dev
Bu komut, nodemon ve ts-node kullanarak sunucunuzu başlatacaktır. Tarayıcınızda http://localhost:3000 adresine giderek "Merhaba Dünya!" mesajını görmelisiniz. http://localhost:3000/api/users adresine gittiğinizde ise JSON formatında kullanıcı listesini almalısınız. Şimdi src/index.ts dosyasında küçük bir değişiklik yapıp kaydedin; nodemon sunucuyu otomatik olarak yeniden başlatacaktır. Böylece hızlı ve verimli bir geliştirme döngüsü elde etmiş olursunuz.
Proje Yapısı ve Geliştirme Best Practice'leri
Büyük ve karmaşık bir Express TypeScript projesinde düzenli bir yapıya sahip olmak, projenin okunabilirliğini, bakımını ve ölçeklenebilirliğini doğrudan etkiler. Dağınık bir kod tabanı, yeni geliştiricilerin projeye adapte olmasını zorlaştırır, hata ayıklama süreçlerini uzatır ve yeni özellik ekleme maliyetini artırır. Bu bölümde, önerilen bir proje yapısı ve TypeScript Express uygulamalarınızı geliştirirken uygulayabileceğiniz bazı en iyi uygulamaları inceleyeceğiz.
Önerilen Dizin Yapısı:
Tipik bir Express TypeScript projesi için aşağıdaki dizin yapısı, modülerliği ve sorumluluk ayrımını destekler:
.
├── dist/ # Derlenmiş JavaScript çıktıları (bu dizini git'e eklemeyin)
├── node_modules/ # Kurulumlu bağımlılıklar
├── src/ # Tüm TypeScript kaynak kodları
│ ├── config/ # Uygulama yapılandırmaları (DB bağlantısı, port vb.)
│ ├── controllers/ # İstekleri işleyen ve yanıtları döndüren fonksiyonlar (iş mantığı değil)
│ ├── services/ # İş mantığı, veritabanı işlemleri, harici API çağrıları
│ ├── routes/ # Express rotaları tanımları
│ ├── middleware/ # Özel middleware'ler (kimlik doğrulama, hata yönetimi vb.)
│ ├── models/ # Veritabanı modelleri ve tipler (örneğin Mongoose şemaları)
│ ├── utils/ # Yardımcı fonksiyonlar (formatlama, doğrulama vb.)
│ └── index.ts # Uygulamanın başlangıç noktası
├── .env # Ortam değişkenleri (git'e eklemeyin)
├── .eslintrc.js # ESLint yapılandırması
├── .gitignore # Git tarafından göz ardı edilecek dosyalar
├── package.json # Proje bağımlılıkları ve betikleri
├── tsconfig.json # TypeScript derleyici yapılandırması
└── README.md # Proje açıklaması
Bu yapılandırma, her bir sorumluluğun kendi dizininde gruplandırılmasını sağlar. Örneğin, tüm rota tanımları routes dizininde, iş mantığı services dizininde ve veri yapıları models dizininde bulunur. Bu sayede kodunuz daha düzenli, okunabilir ve yönetilebilir hale gelir.
Modüler Yapılandırma:
Uygulamanızın yapılandırma ayarlarını (veritabanı bağlantı dizeleri, API anahtarları, port numaraları vb.) ayrı bir config dizininde tutmak iyi bir uygulamadır. Bu, farklı ortamlar (geliştirme, test, üretim) için kolayca farklı ayarlar kullanmanıza olanak tanır. Ortam değişkenlerini yönetmek için dotenv kütüphanesini kullanabilirsiniz.
// src/config/index.ts
import dotenv from 'dotenv';
dotenv.config(); // .env dosyasındaki değişkenleri process.env'ye yükler
export const config = {
port: process.env.PORT || 3000,
databaseUrl: process.env.DATABASE_URL || 'mongodb://localhost:27017/mydb',
jwtSecret: process.env.JWT_SECRET || 'supersecretkey',
env: process.env.NODE_ENV || 'development'
};
Uygulamanızda bu yapılandırma değerlerine import { config } from './config'; şeklinde erişebilirsiniz. .env dosyası ise hassas bilgileri içerdiği için mutlaka .gitignore dosyasına eklenmelidir.
Hata Yönetimi (Middleware):
Express uygulamalarında global hata yakalama mekanizması kurmak, kullanıcılarınıza daha tutarlı bir hata mesajı sunmanın ve sunucunuzun çökmesini engellemenin anahtarıdır. Özel bir hata yakalama middleware'i oluşturarak tüm hataları tek bir noktadan yönetebilirsiniz.
// src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack); // Hata detaylarını logla
res.status(err.statusCode || 500).json({
message: err.message || 'Bir sunucu hatası oluştu.',
error: process.env.NODE_ENV === 'development' ? err : {} // Geliştirme modunda hata detaylarını göster
});
};
Bu middleware'i src/index.ts dosyanızda tüm rotalardan sonra çağırarak etkinleştirebilirsiniz.
// src/index.ts içinde
// ... diğer rotalar ve middleware'ler
import { errorHandler } from './middleware/errorHandler';
app.use(errorHandler); // En sona ekleyin
Linting ve Biçimlendirme (ESLint, Prettier):
Kod kalitesini ve tutarlılığını sağlamak için ESLint ve Prettier kullanmak vazgeçilmezdir. ESLint kodunuzdaki potansiyel hataları ve stil ihlallerini tespit ederken, Prettier kodunuzu otomatik olarak belirli bir stile göre biçimlendirir. Bu araçlar, özellikle çok kişilik ekiplerde kodun aynı standartta kalmasını sağlar ve gereksiz "kod stili" tartışmalarını ortadan kaldırır.
npm install --save-dev eslint prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-prettier eslint-plugin-prettier
Ardından .eslintrc.js ve .prettierrc dosyalarını yapılandırarak projeniz için uygun kuralları belirleyebilirsiniz. Bu uygulamalar, sadece kodunuzu daha okunabilir hale getirmekle kalmaz, aynı zamanda gelecekteki bakım süreçlerini de kolaylaştırır. Bir proje büyüdükçe, bu tür "best practice"lerin faydaları katlanarak artar.
Vaka Analizi: Bir SaaS Uygulamasının Modüler Yapısı
Bulut tabanlı bir proje yönetim SaaS uygulaması geliştiren "ProjeX" ekibi, başlangıçta monolitik bir JavaScript yapısıyla yola çıktı. Ancak uygulama büyüdükçe, yeni özellik eklemek veya mevcut kodda değişiklik yapmak giderek zorlaştı. Özellikle farklı geliştiricilerin aynı dosya üzerinde çalışması, merge çakışmalarına ve beklenmedik yan etkilere yol açıyordu. Bu sorunları çözmek için ProjeX, uygulamasını Express TypeScript tabanlı, modüler bir yapıya geçirme kararı aldı. controllers, services, routes, models gibi dizinler oluşturuldu ve her modülün kendi içinde bu yapıya uyması sağlandı. Örneğin, "Kullanıcı Yönetimi" modülü kendi user.controller.ts, user.service.ts ve user.routes.ts dosyalarına sahip oldu. Bu geçişin ardından, yeni özellik geliştirme süresi %25 kısaldı, çünkü geliştiriciler artık tüm kod tabanını anlamak zorunda kalmadan sadece ilgili modüle odaklanabiliyorlardı. Ayrıca, otomatik test kapsamı artırıldı ve hata ayıklama süreçleri kolaylaştı. Modüler yapı, ProjeX'in uygulamasını daha sürdürülebilir hale getirerek, gelecekteki genişlemeler ve mikroservis mimarisine geçiş için sağlam bir temel oluşturdu.
İleri Düzey Konular: Veritabanı Entegrasyonu ve API Tasarımı
Bir Express TypeScript sunucusunun temelini kurduktan sonra, çoğu web uygulamasının olmazsa olmazı olan veritabanı entegrasyonu ve API tasarım prensiplerine odaklanmak önemlidir. Backend uygulamalarının çoğu, verileri kalıcı olarak depolamak ve yönetmek için bir veritabanına ihtiyaç duyar. Seçtiğiniz veritabanı türü (SQL veya NoSQL) ve ona erişim yönteminiz, uygulamanızın performansını ve ölçeklenebilirliğini doğrudan etkileyecektir.
Veritabanı Bağlantısı ve ORM Kullanımı
Yaygın olarak kullanılan iki veritabanı türü vardır: ilişkisel (SQL) veritabanları (PostgreSQL, MySQL vb.) ve ilişkisel olmayan (NoSQL) veritabanları (MongoDB, Redis vb.). TypeScript ile çalışırken, bu veritabanları için tip güvenliği sağlayan adaptörler veya Object-Relational Mapper (ORM) / Object-Document Mapper (ODM) kütüphaneleri kullanmak geliştirme sürecini büyük ölçüde kolaylaştırır.
MongoDB ile Mongoose ve TypeScript:
MongoDB popüler bir NoSQL veritabanıdır ve Mongoose, Node.js için resmi olmasa da en yaygın kullanılan ODM'dir. Mongoose, şema tabanlı bir çözüm sunar ve MongoDB ile tip güvenli çalışmak için harikadır. İlk olarak Mongoose'u ve tip tanımlarını kuralım:
npm install mongoose @types/mongoose
# veya yarn kullanıyorsanız:
# yarn add mongoose @types/mongoose
Ardından, bir veritabanı bağlantısı ve basit bir model oluşturalım:
// src/config/database.ts
import mongoose from 'mongoose';
import { config } from './index'; // Önceki config dosyasından
export const connectDB = async () => {
try {
await mongoose.connect(config.databaseUrl);
console.log('MongoDB bağlantısı başarılı.');
} catch (error) {
console.error('MongoDB bağlantı hatası:', error);
process.exit(1); // Uygulamayı sonlandır
}
};
// src/models/User.ts
import { Schema, model, Document } from 'mongoose';
export interface IUser extends Document {
username: string;
email: string;
createdAt: Date;
}
const UserSchema = new Schema
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
createdAt: { type: Date, default: Date.now },
});
const User = model
export default User;
Bu modeli kullanarak src/services/userService.ts içinde kullanıcı işlemleri yapabilir ve src/controllers/userController.ts içinde bunları rotalara bağlayabilirsiniz. IUser interface'i, kullanıcı nesnelerinizin tip güvenliğini sağlar.
PostgreSQL ile TypeORM ve TypeScript:
İlişkisel veritabanları için TypeORM, güçlü bir ORM çözümüdür. TypeScript'in avantajlarından tam olarak yararlanır ve entity'ler, repository'ler ile çalışmayı kolaylaştırır. PostgreSQL'i bir örnek olarak alalım:
npm install typeorm pg reflect-metadata
npm install --save-dev @types/pg
# tsconfig.json dosyanızda "emitDecoratorMetadata": true ve "experimentalDecorators": true ayarlarını etkinleştirmeyi unutmayın
// src/config/database.ts (TypeORM için)
import { DataSource } from 'typeorm';
import { User } from '../models/User'; // TypeORM Entity'niz
export const AppDataSource = new DataSource({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'your_username',
password: 'your_password',
database: 'your_database',
synchronize: true, // Geliştirme için true, üretim için false
logging: false,
entities: [User],
migrations: [],
subscribers: [],
});
export const connectDB = async () => {
try {
await AppDataSource.initialize();
console.log('PostgreSQL bağlantısı başarılı.');
} catch (error) {
console.error('PostgreSQL bağlantı hatası:', error);
process.exit(1);
}
};
// src/models/User.ts (TypeORM için)
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id!: number;
@Column({ unique: true })
username!: string;
@Column({ unique: true })
email!: string;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt!: Date;
}
Her iki örnekte de, connectDB fonksiyonunu src/index.ts dosyanızda sunucu başlamadan önce çağırmalısınız.
RESTful API Prensipleri ve TypeScript'te Uygulanışı
API'leriniz, client uygulamaların sunucunuzla etkileşim kurduğu temel arayüzdür. RESTful prensiplerine uygun API'ler tasarlamak, API'lerinizin daha anlaşılır, ölçeklenebilir ve sürdürülebilir olmasını sağlar.
- Kaynak Odaklılık: API'lerinizi "kullanıcılar", "ürünler", "siparişler" gibi kaynaklar etrafında tasarlayın. URL'leriniz isimler (çoğul) içermelidir:
/api/users,/api/products. - HTTP Metodları: Her HTTP metodu (GET, POST, PUT, DELETE) belirli bir CRUD (Create, Read, Update, Delete) işlemine karşılık gelmelidir.
GET /api/users: Tüm kullanıcıları getir.GET /api/users/:id: Belirli bir kullanıcıyı getir.POST /api/users: Yeni bir kullanıcı oluştur.PUT /api/users/:id: Belirli bir kullanıcıyı güncelle (tüm alanlar).PATCH /api/users/:id: Belirli bir kullanıcıyı kısmen güncelle (sadece değişen alanlar).DELETE /api/users/:id: Belirli bir kullanıcıyı sil.
- Durumsuzluk (Stateless): Sunucu, client'tan gelen her isteği bağımsız olarak işlemelidir. Oturum bilgileri gibi durumlar client tarafından yönetilmelidir (örneğin JWT ile).
- Hata Kodları: Standart HTTP durum kodlarını (200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error) kullanarak API'nizin durumunu açıkça belirtin.
TypeScript, API tasarımında önemli bir rol oynar. İstek gövdeleri (request body), yanıt yapıları (response payload) ve parametreler için arayüzler (interfaces) tanımlayarak, API'nizin sözleşmesini net bir şekilde belirtirsiniz. Bu, hem backend hem de frontend geliştiricilerin daha güvenli ve uyumlu kod yazmasını sağlar.
// src/controllers/userController.ts
import { Request, Response } from 'express';
import User, { IUser } from '../models/User'; // Mongoose modelini import edin
interface CreateUserRequestBody {
username: string;
email: string;
}
export const createUser = async (req: Request, res: Response) => {
try {
const { username, email } = req.body;
const newUser: IUser = new User({ username, email });
await newUser.save();
res.status(201).json(newUser);
} catch (error: any) {
res.status(500).json({ message: error.message });
}
};
export const getUsers = async (req: Request, res: Response) => {
try {
const users = await User.find();
res.status(200).json(users);
} catch (error: any) {
res.status(500).json({ message: error.message });
}
};
Yukarıdaki örnekte CreateUserRequestBody arayüzü, createUser fonksiyonuna gelen isteğin gövdesinin tipini tanımlar. Bu sayede req.body'den gelen verilere erişirken tip güvenliği sağlanır ve olası hatalar geliştirme aşamasında yakalanır. Bu yaklaşım, API'lerinizi daha sağlam ve hata dirençli hale getirir.
Middleware Kullanımı:
Express'in middleware yapısı, her isteğin işlenmeden önce veya sonra belirli işlemlerin yapılmasını sağlar. Kimlik doğrulama, yetkilendirme, loglama veya veri doğrulama gibi işlemleri middleware olarak tanımlayarak kod tekrarını önleyebilirsiniz.
// src/middleware/authMiddleware.ts
import { Request, Response, NextFunction } from 'express';
export const authenticate = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'Yetkilendirme tokenı bulunamadı.' });
}
// Token doğrulama mantığı buraya gelir (örneğin JWT doğrulama)
// Eğer geçerliyse, req.user gibi bir nesneye kullanıcı bilgilerini ekleyebiliriz
// req.user = decodedToken.user;
next(); // Bir sonraki middleware veya route handler'a geç
};
Bu authenticate middleware'i, bir rota tanımlarken kullanılabilir:
// src/routes/userRoutes.ts
import { Router } from 'express';
import { getUsers, createUser } from '../controllers/userController';
import { authenticate } from '../middleware/authMiddleware';
const router = Router();
router.get('/users', authenticate, getUsers); // Kullanıcıları getirmeden önce kimlik doğrula
router.post('/users', createUser); // Kimlik doğrulama gerekmeyebilir
export default router;
Bu entegrasyonlar ve tasarım prensipleri, Express TypeScript uygulamanızın sadece çalışmasını değil, aynı zamanda uzun vadede bakımı kolay, güvenli ve performanslı olmasını sağlar.
Deploy ve Bakım: Uygulamanızı Canlıya Alma ve Sürdürülebilirlik
Bir Express TypeScript sunucusunu başarıyla geliştirmek kadar, onu üretim ortamına dağıtmak (deploy etmek) ve uzun vadede sürdürülebilirliğini sağlamak da kritik öneme sahiptir. Geliştirme ortamında her şey yolunda giderken, üretim ortamında farklı zorluklarla karşılaşabilirsiniz. Bu bölümde, uygulamanızı canlıya alma adımlarını ve gelecekteki bakım süreçlerini kolaylaştıracak stratejileri ele alacağız.
Üretim Ortamı İçin Derleme (tsc)
Geliştirme sırasında ts-node ve nodemon ile TypeScript dosyalarını doğrudan çalıştırsak da, üretim ortamında bu yaklaşım önerilmez. Üretimde, TypeScript kodunuzu saf JavaScript'e derlemeli ve bu derlenmiş JavaScript dosyalarını çalıştırmalısınız. Bunun birkaç nedeni vardır: derlenmiş kod daha optimize olabilir, ek bağımlılıklara (TypeScript, ts-node) ihtiyaç duymaz ve genellikle daha hızlı başlar.
Projenizi üretim için derlemek için package.json dosyasında tanımladığımız build betiğini kullanın:
npm run build
# veya yarn kullanıyorsanız:
# yarn build
Bu komut, tsconfig.json ayarlarınıza göre src klasöründeki tüm TypeScript dosyalarınızı dist klasörüne derlenmiş JavaScript dosyaları olarak kopyalayacaktır. Örneğin, src/index.ts dosyası dist/index.js olarak derlenecektir. Artık üretim sunucunuzda sadece dist klasöründeki JavaScript dosyalarını ve node_modules klasörünü barındıran hafif bir paket dağıtabilirsiniz.
PM2 veya Docker ile Dağıtım
Uygulamanızı canlıya aldıktan sonra, onun sürekli çalışır durumda kalmasını, çöktüğünde otomatik olarak yeniden başlamasını ve performansının izlenebilmesini sağlamak önemlidir. Bunun için iki popüler yöntem vardır: PM2 ve Docker.
-
PM2 (Process Manager 2):
Node.js uygulamalarını üretimde çalıştırmak için güçlü bir süreç yöneticisidir. PM2, uygulamanızın otomatik olarak yeniden başlatılmasını, küme modunda (cluster mode) çalıştırılmasını (birden fazla CPU çekirdeğini kullanmak için) ve performans izlemesini sağlar.
npm install -g pm2 # Global olarak kurun # Uygulamanızı başlatın pm2 start dist/index.js --name "my-express-app" # Küme modunda başlatın (örn: 4 çekirdek) # pm2 start dist/index.js -i 4 --name "my-express-app-cluster" # Tüm uygulamaları listele pm2 list # Logları izle pm2 logs my-express-app # Uygulamayı durdur pm2 stop my-express-appPM2, uygulamanızın kesintisiz çalışmasını garanti eder ve sistem çökmelerinde dahi uygulamanızı tekrar ayağa kaldırır.
-
Docker:
Uygulamanızı ve tüm bağımlılıklarını izole edilmiş, taşınabilir bir konteynır içine paketlemenizi sağlar. Bu, "benim makinemde çalışıyordu" sorununu ortadan kaldırır ve uygulamalarınızın her ortamda (geliştirme, test, üretim) aynı şekilde davranmasını garanti eder. Modern dağıtım yaklaşımlarında Docker vazgeçilmezdir.
# Dockerfile # Node.js'in resmi slim sürümünü temel alır FROM node:18-alpine # Uygulama kodunun bulunacağı çalışma dizini WORKDIR /app # package.json ve package-lock.json dosyalarını kopyala COPY package*.json ./ # Bağımlılıkları kur RUN npm install # Uygulama kaynak kodlarını kopyala COPY . . # TypeScript kodunu JavaScript'e derle RUN npm run build # Uygulamanın dinleyeceği portu belirt EXPOSE 3000 # Uygulamayı başlat CMD ["npm", "start"]Bu
Dockerfileile uygulamanızı bir Docker imajına dönüştürebilir ve herhangi bir Docker uyumlu ortamda çalıştırabilirsiniz:docker build -t my-express-app . docker run -p 3000:3000 my-express-appDocker, özellikle mikroservis mimarileri ve CI/CD (Sürekli Entegrasyon/Sürekli Dağıtım) boru hatlarında dağıtımı otomatikleştirir ve standartlaştırır.
Günlükleme (Logging) ve İzleme (Monitoring)
Üretimdeki bir uygulamanın sağlığını ve performansını anlamak için günlükleme ve izleme sistemleri hayati öneme sahiptir. Hataları tespit etmek, performans darboğazlarını bulmak ve uygulamanızın davranışını anlamak için ayrıntılı günlükler tutmalısınız. Winston veya Pino gibi kütüphaneler, yapılandırılmış günlükler yazmak için popüler seçimlerdir.
// src/utils/logger.ts
import winston from 'winston';
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
export default logger;
Uygulamanızda logger.info('Kullanıcı giriş yaptı'); veya logger.error('Bir hata oluştu', error); şeklinde kullanabilirsiniz. Ayrıca Prometheus, Grafana veya Datadog gibi izleme araçları kullanarak CPU, bellek kullanımı, istek süreleri gibi metrikleri toplayabilir ve görselleştirebilirsiniz.
Sürdürülebilirlik İpuçları: Test, Dokümantasyon, Güvenlik
- Test: Uygulamanız için kapsamlı bir test paketi yazın (birim testleri, entegrasyon testleri, uçtan uca testler). Jest veya Mocha/Chai gibi kütüphaneler TypeScript ile iyi çalışır. Testler, kod değişikliklerinin beklenmedik yan etkiler yaratmamasını sağlar ve refaktoring işlemlerini güvenli hale getirir.
- Dokümantasyon: API'nizin nasıl kullanılacağını, kaynaklarını ve uç noktalarını açıklayan net bir dokümantasyon (örneğin Swagger/OpenAPI) hazırlayın. Bu, hem frontend ekibi hem de diğer servislerle entegrasyonu kolaylaştırır.
- Güvenlik: Temel güvenlik önlemlerini (CORS, Helmet, rate limiting, veri doğrulama) uygulayın. Bağımlılıklarınızı düzenli olarak güncel tutun ve bilinen güvenlik açıklarını takip edin. Ortam değişkenlerini hassas bilgiler için kullanın ve bunları versiyon kontrolüne eklemeyin.
Aşağıdaki tablo, geliştirme ve üretim ortamları arasındaki temel farkları özetlemektedir:
| Özellik | Geliştirme Ortamı | Üretim Ortamı |
|---|---|---|
| Derleme | ts-node ile anında (JIT) |
Önceden tsc ile derlenmiş JavaScript |
| Yeniden Başlatma | nodemon ile otomatik |
pm2 veya Docker ile sürekli ve hata toleranslı |
| Hata Mesajları | Ayrıntılı hata yığınları | Genel, kullanıcılara yönelik mesajlar |
| Performans | Hızlı geliştirme döngüsü odaklı | Optimizasyon, hız ve kaynak verimliliği odaklı |
| Bağımlılıklar | devDependencies dahil tümü |
Sadece dependencies (derlenmiş kod) |
| Günlükleme | debug veya info seviyesi, konsol çıktısı |
info, warn, error seviyesi, dosyalara veya merkezi log sistemlerine |
Bu stratejileri uygulayarak, Express TypeScript uygulamanızın yalnızca başarılı bir şekilde geliştirilmesini değil, aynı zamanda canlıya alındıktan sonra da kararlı, güvenli ve kolayca yönetilebilir olmasını sağlayabilirsiniz.
Sonuç ve Sıkça Sorulan Sorular
Bu kapsamlı rehber boyunca, modern web uygulamaları geliştirmek için güçlü bir ikili olan Express ve TypeScript'in neden bu kadar popüler olduğunu, bu teknolojilerin temel kavramlarını, adım adım bir Express TypeScript sunucusunun nasıl kurulduğunu, proje yapısı için en iyi uygulamaları, veritabanı entegrasyonu gibi ileri düzey konuları ve son olarak uygulamanızı üretim ortamına nasıl dağıtacağınızı ve sürdürülebilirliğini nasıl sağlayacağınızı öğrendiniz. TypeScript'in sunduğu tip güvenliği ve geliştirici verimliliği ile Express'in esnekliği ve hafifliği, bir araya geldiğinde sağlam, ölçeklenebilir ve bakımı kolay backend sistemleri oluşturmanın anahtarını sunar. Bu rehberde edindiğiniz bilgilerle, kendi projelerinizi başlatmak ve mevcut becerilerinizi geliştirmek için sağlam bir temel oluşturmuş oldunuz. Artık kendi Express TypeScript sunucularınızı kurmaya ve modern web'in ihtiyaç duyduğu uygulamaları geliştirmeye hazırsınız.
Sıkça Sorulan Sorular
-
Express TypeScript projesinde
srcvedistklasörlerinin amacı nedir?Cevap:
src(source) klasörü, tüm orijinal TypeScript kaynak kodunuzu içerir.dist(distribution) klasörü isesrcklasöründeki TypeScript kodunuzun TypeScript derleyicisi (tsc) tarafından JavaScript'e derlenmiş halini içerir. Üretim ortamında çalıştırılan kod,distklasöründeki JavaScript dosyalarıdır. Bu ayrım, kaynak kodunuzu derlenmiş çıktıdan temiz tutar ve dağıtım paketinizin sadece gerekli dosyaları içermesini sağlar. -
nodemonvets-nodeneden geliştirme bağımlılıkları (devDependencies) olarak kurulur?Cevap:
nodemonvets-node, sadece geliştirme sürecini kolaylaştırmak için kullanılan araçlardır (kod değişikliklerinde otomatik yeniden başlatma, TypeScript'i anında çalıştırma). Üretim ortamında uygulamanız zaten derlenmiş JavaScript kodunu çalıştıracağı için bu araçlara ihtiyaç duymaz. Bu nedenle, projenizin üretim paketini gereksiz yere büyütmemek adınadevDependenciesolarak kurulmaları en iyi uygulamadır. -
Bir Express TypeScript uygulamasında ortam değişkenlerini (
.env) nasıl güvenli bir şekilde yönetirim?Cevap: Ortam değişkenlerini yönetmenin en güvenli yolu,
dotenvgibi bir kütüphane kullanarak.envdosyasında tanımlamak ve bu.envdosyasını kesinlikle.gitignoredosyasına ekleyerek versiyon kontrol sistemine (Git) dahil etmemektir. Üretim ortamında, bu değişkenler sunucu sağlayıcınızın (AWS, Heroku, DigitalOcean vb.) ortam değişkeni ayarları üzerinden doğrudan sisteme enjekte edilir, böylece hassas bilgiler kod tabanında veya versiyon kontrolünde saklanmamış olur. -
API'lerimde kimlik doğrulama (authentication) ve yetkilendirme (authorization) işlemlerini nasıl uygularım?
Cevap: Kimlik doğrulama ve yetkilendirme işlemleri için genellikle middleware fonksiyonları kullanılır. Kimlik doğrulama için JSON Web Tokens (JWT) popüler bir çözümdür; kullanıcı giriş yaptığında bir token oluşturulur ve sonraki her istekte bu token gönderilir. Sunucu, gelen token'ı doğrulayarak kullanıcının kimliğini onaylar. Yetkilendirme için ise, kimliği doğrulanmış kullanıcının rollerine veya izinlerine bakılarak belirli bir kaynağa erişip erişemeyeceği kontrol edilir. Bu işlemler, Express'in middleware zincirine eklenen özel fonksiyonlar aracılığıyla gerçekleştirilir.
-
TypeORM veya Mongoose kullanırken
synchronize: trueayarını üretimde nedenfalseyapmalıyım?Cevap:
synchronize: trueayarı, TypeORM veya Mongoose'un modellerinize göre veritabanı şemasını otomatik olarak oluşturmasını veya güncellemesini sağlar. Bu, geliştirme aşamasında hızlı prototipleme için çok kullanışlıdır. Ancak üretim ortamında, bu ayar veri kaybına yol açabilecek potansiyel riskler taşır (örneğin, şema değişiklikleri mevcut verileri silebilir). Üretimde, veritabanı şema değişikliklerini manuel olarak (migration dosyaları ile) yönetmek ve dikkatli bir şekilde uygulamak çok daha güvenlidir. Bu nedenlesynchronize: truegenellikledevelopmentortamında kullanılırken,productionortamındafalseolmalıdır.