Ön Hazırlık ve Gereksinimler
Bu rehberi uygulamak için sisteminizde Node.js 20 veya daha yeni bir sürümün yüklü olması önerilir. Projenizde Express.js çatısını ve modern JavaScript (ES Modules) sözdizimini kullanacağız. Hata yönetimi için herhangi bir harici kütüphaneye bağımlı kalmadan, Node.js'in yerleşik Error sınıfını genişleterek kendi yapımızı kuracağız.
- Node.js (LTS sürümü)
- Express.js (Web çatısı)
- Temel JavaScript (Async/Await bilgisi)
Adım 1: Özel Hata Sınıfları Oluşturma
Node.js'in standart Error sınıfı, çok katmanlı bir mimaride hangi katmandan hata geldiğini veya hatanın HTTP karşılığının ne olduğunu belirtmekte yetersiz kalır. Bu yüzden, AppError adında bir sınıf oluşturarak hatalarımıza durum kodu (status code) ve operasyonel olup olmadığı bilgisini ekleyeceğiz.
// utils/AppError.js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true; // Beklenen hatalar için işaretleyici
Error.captureStackTrace(this, this.constructor);
}
}
export default AppError;
Bu sınıf, uygulamamızın her yerinde fırlatacağımız hataların standart bir yapıya sahip olmasını sağlar. isOperational özelliği, programlama hatası ile kullanıcı kaynaklı hataları ayırt etmemize yardımcı olur.
Adım 2: Merkezi Hata Yakalama Middleware'i
Express.js içerisinde her rotada try-catch bloğu yazmak yerine, merkezi bir hata işleyici (error handling middleware) kullanmalıyız. Bu yapı, uygulamanın herhangi bir yerinde fırlatılan hatayı yakalar ve kullanıcıya tutarlı bir JSON yanıtı döner.
// middlewares/errorMiddleware.js
export const globalErrorHandler = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
res.status(err.statusCode).json({
status: err.status,
message: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
});
};
Bu middleware, üretim ortamında (production) hata detaylarını gizlerken, geliştirme ortamında hata izlerini (stack trace) göstererek geliştiriciye yardımcı olur.
Adım 3: Katmanlı Mimari İçin Hata Yönetim Stratejisi
Çok katmanlı mimaride hata yönetimi, hatanın nerede oluştuğuna bağlı olarak değişir. Data Access katmanı (veritabanı işlemleri), Service katmanı (iş mantığı) ve Controller katmanı (istek yönetimi) arasında veriyi nasıl taşıyacağımızı inceleyelim.
| Katman | Hata Yönetim Yöntemi | Sorumluluk |
|---|---|---|
| Controller | Hataları yakala ve merkezi işleyiciye ilet | HTTP yanıtı oluşturma |
| Service | İş mantığı hatalarını fırlat | Hata kontrolü ve iş kuralları |
| Data Access | Veritabanı hatalarını sarmala | Veri bütünlüğü |
Adım 4: Controller Katmanında Hata Yakalama
Controller katmanında async fonksiyonları her seferinde try-catch içine almak yerine, bir "catchAsync" yardımcı fonksiyonu kullanmak kod tekrarını önler. Bu yaklaşım, hata yönetimini daha temiz kılar.
// utils/catchAsync.js
export const catchAsync = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};
// controllers/userController.js
import { catchAsync } from '../utils/catchAsync.js';
import AppError from '../utils/AppError.js';
export const getUser = catchAsync(async (req, res, next) => {
const user = await userService.findById(req.params.id);
if (!user) {
return next(new AppError('Kullanıcı bulunamadı', 404));
}
res.status(200).json({ status: 'success', data: user });
});
Bu yöntemle, Controller katmanında hata oluştuğunda next() fonksiyonuna hata nesnesini göndererek, merkezi hata işleyicimizin devreye girmesini sağlıyoruz.
Adım 5: Service Katmanında Hata Denetimi
Service katmanı, iş mantığının kalbidir. Burada veritabanından gelen veriyi kontrol etmeli ve iş kurallarına aykırı bir durum varsa AppError fırlatmalıyız.
// services/userService.js
import AppError from '../utils/AppError.js';
export const findById = async (id) => {
const user = await db.users.find(id);
if (!user) {
throw new AppError('Veritabanında bu ID ile kayıt yok', 404);
}
return user;
};
Burada fırlatılan hata, Controller tarafından yakalanacak ve merkezi işleyiciye iletilecektir. Bu, katmanlar arası iletişimi standartlaştırır.
Kritik Güvenlik Uyarısı: Hata mesajlarında veritabanı şeması, tablo isimleri veya hassas sistem bilgileri (şifreler, API anahtarları) asla kullanıcıya döndürülmemelidir. Üretim ortamında her zaman genel, güvenli mesajlar (Örn: "Bir hata oluştu") kullanılmalıdır.
Adım 6: Beklenmedik Hataları Yönetmek
Uygulamanın hiç beklemediği (örneğin veritabanı bağlantısının kopması) hatalar için process seviyesinde dinleyiciler eklemeliyiz. Bu, uygulamanın sessizce ölmesini engeller.
// app.js
process.on('unhandledRejection', (err) => {
console.error('Unhandled Rejection! Uygulama kapatılıyor...');
console.error(err.name, err.message);
process.exit(1);
});
Bu kod bloğu, promise zincirlerinde yakalanmamış hataları yakalar ve uygulamanın güvenli bir şekilde yeniden başlatılması için süreci sonlandırır.
Sıkça Sorulan Sorular
Neden özel bir hata sınıfı kullanmalıyım?
Standart Error sınıfı, HTTP durum kodları gibi web uygulamasına özgü verileri taşımak için yeterli değildir. Özel sınıf, hataları sınıflandırmanızı ve merkezi işleyicide farklı tepkiler vermenizi sağlar.
Async/Await kullanırken try-catch neden zorunludur?
Node.js'te async fonksiyonlar bir Promise döner. Bu Promise içinde oluşan bir hata, try-catch veya .catch() ile yakalanmazsa "unhandled rejection" hatasına sebep olur ve uygulamanın kararlılığını bozar.
Üretim ortamında stack trace neden gizlenmeli?
Stack trace, kodunuzun dosya yapısını, kullanılan kütüphaneleri ve hata oluşan satırları açıkça gösterir. Bu bilgiler, kötü niyetli kişiler için sisteminizdeki zafiyetleri anlamalarını kolaylaştıran bir harita görevi görür.
isOperational bayrağı neden önemlidir?
Bu bayrak, hatanın sizin tarafınızdan beklenen (validation hatası gibi) mi yoksa beklenmedik bir yazılım hatası (bug) mı olduğunu ayırt etmenizi sağlar. Beklenmedik hatalar için loglama seviyesini artırabilirsiniz.
Global hata işleyiciyi Express'te nereye yerleştirmeliyim?
Tüm route tanımlamalarından sonra, en sona yerleştirilmelidir. Express, middleware'leri tanımlanma sırasına göre çalıştırır; bu nedenle hata işleyicinin tüm rotaları kapsayabilmesi için en sonda olması şarttır.
Sorumluluk Reddi: Bu rehberdeki kod örnekleri eğitim amaçlıdır. Uygulamanızın güvenlik gereksinimlerine göre (XSS, SQL Injection, CSRF) ek koruma önlemleri almanız ve bağımlılıklarınızı düzenli olarak güncellemeniz gerekmektedir.
Hataları İzlenebilir Kılmak: Winston ile Loglama Entegrasyonu
Hata yönetimi sadece kullanıcıya anlamlı bir mesaj döndürmekle sınırlı değildir; arka planda gerçekleşen hataların izini sürebilmek, sistemin sürdürülebilirliği için kritiktir. Merkezi hata işleyicimizi, hataları dosyalara veya harici servislere yazan Winston kütüphanesi ile güçlendirebiliriz.
Aşağıdaki örnekte, hata middleware'imizi nasıl bir loglama mekanizmasıyla birleştirebileceğinizi görebilirsiniz:
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.Console({ format: winston.format.simple() })
]
});
const globalErrorHandler = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
// Hatayı log dosyasına kaydet
logger.error({
message: err.message,
stack: err.stack,
path: req.path,
method: req.method
});
res.status(err.statusCode).json({
status: 'error',
message: err.message
});
};
Hata Yönetiminde Performans Optimizasyonu
Çok katmanlı mimarilerde hata yönetimi, gereksiz işlem yükü oluşturmamalıdır. Özellikle yüksek trafikli uygulamalarda, her hata durumunda ağır işlemler (e-posta gönderimi, veritabanı yazma vb.) yapmak performansı olumsuz etkileyebilir. Bu noktada dikkat etmeniz gereken temel prensipler şunlardır:
- Stack Trace Maliyeti: Hataları loglarken
stack traceverisini veritabanına değil, sadece log dosyalarına yazın. - Asenkron İşlemler: Loglama işlemlerini ana iş akışını bloklamayacak şekilde (asenkron) yapılandırın.
- Hata Filtreleme: 404 gibi beklenen veya "operational" olmayan hataları, sistem kaynaklarını tüketmemesi için ayrı bir kategoride değerlendirin.
Aşağıdaki tablo, hata tiplerine göre izlenmesi gereken stratejiyi özetlemektedir:
| Hata Türü | Öncelik | Aksiyon |
|---|---|---|
| Operational (4xx) | Düşük | Kullanıcıya bildir, logla. |
| Programmatic (5xx) | Kritik | Logla, admini uyar, sistemi kapat/yeniden başlat. |
Bu stratejiler, uygulamanızın hata anında bile yüksek performansla çalışmasını ve geliştiricilerin hataları hızlıca teşhis etmesini sağlar. Unutmayın, iyi bir hata yönetimi sistemi, uygulamanın "sessizce çökmesini" engelleyen bir güvenlik ağıdır.
Sonuç
Node.js ile çok katmanlı mimaride hata yönetimi, uygulamanızın profesyonelliğini belirleyen temel taşlardan biridir. Merkezi bir hata işleyici, özelleştirilmiş hata sınıfları ve katmanlar arası standart iletişim sayesinde, hem geliştirme sürecini kolaylaştırabilir hem de kullanıcılarınıza daha güvenli bir deneyim sunabilirsiniz. Bir sonraki adım olarak, bu hata yönetim sistemini bir loglama kütüphanesi (Winston veya Pino gibi) ile entegre ederek hatalarınızı harici bir dosyaya veya bulut servisine kaydetmeyi öğrenebilirsiniz.


Yorumlar (0)
Yorum Yaz