Node.js Error Handling: Production-Ready Hata Yönetimi

Node.js Error Handling: Production-Ready Hata Yönetimi
Hata yönetimi, production-ready bir Node.js uygulamasının en kritik bileşenlerinden biridir. Doğru hata yönetimi stratejisi, uygulamanızın güvenilirliğini artırır, debugging sürecini kolaylaştırır ve kullanıcı deneyimini iyileştirir. Bu rehberde Node.js'te hata türlerini, operational ve programming error ayrımını, Express.js'te merkezi hata yönetimini, async error handling pattern'lerini ve production ortamı için en iyi pratikleri kapsamlı şekilde inceleyeceğiz.
1. Node.js'te Hata Türleri
Node.js'te hatalar iki ana kategoriye ayrılır: Operational errors (beklenen, yönetilebilir hatalar) ve Programming errors (kod hataları, bug'lar). Bu ayrım, hata yönetimi stratejinizin temelini oluşturur. Operational error'lar (veritabanı bağlantı hatası, geçersiz kullanıcı girişi, timeout) graceful şekilde yönetilmelidir. Programming error'lar (undefined property erişimi, type error) ise genellikle uygulamanın yeniden başlatılmasını gerektirir.
// Özel hata sınıfları hiyerarşisi
class AppError extends Error {
constructor(message, statusCode, errorCode) {
super(message);
this.name = this.constructor.name;
this.statusCode = statusCode;
this.errorCode = errorCode;
this.isOperational = true;
this.timestamp = new Date().toISOString();
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
constructor(errors) {
super('Doğrulama hatası', 400, 'VALIDATION_ERROR');
this.errors = errors;
}
}
class NotFoundError extends AppError {
constructor(resource = 'Kaynak') {
super(`${resource} bulunamadı`, 404, 'NOT_FOUND');
}
}
class AuthenticationError extends AppError {
constructor(message = 'Kimlik doğrulama başarısız') {
super(message, 401, 'AUTHENTICATION_ERROR');
}
}
class AuthorizationError extends AppError {
constructor(message = 'Bu işlem için yetkiniz yok') {
super(message, 403, 'AUTHORIZATION_ERROR');
}
}
class ConflictError extends AppError {
constructor(message = 'Kaynak zaten mevcut') {
super(message, 409, 'CONFLICT');
}
}
class RateLimitError extends AppError {
constructor() {
super('Çok fazla istek, lütfen daha sonra tekrar deneyin', 429, 'RATE_LIMIT');
}
}
module.exports = {
AppError, ValidationError, NotFoundError,
AuthenticationError, AuthorizationError,
ConflictError, RateLimitError
};
2. Express.js Merkezi Hata Yönetimi
Express.js'te tüm hataları tek bir noktada yakalamak, tutarlı hata yanıtları döndürmek ve hataları loglamak için global error handling middleware kullanılır. Bu middleware, uygulamanın en son middleware'i olarak tanımlanmalıdır.
// Async handler wrapper
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Controller'da kullanım
const getUserById = asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError('Kullanıcı');
}
res.json({ success: true, data: user });
});
// Global Error Handler Middleware
const errorHandler = (err, req, res, next) => {
// Varsayılan değerler
let statusCode = err.statusCode || 500;
let message = err.message || 'Sunucu hatası';
let errorCode = err.errorCode || 'INTERNAL_ERROR';
// Mongoose hataları
if (err.name === 'CastError') {
statusCode = 400;
message = `Geçersiz ${err.path} değeri: ${err.value}`;
errorCode = 'INVALID_ID';
}
if (err.code === 11000) {
statusCode = 409;
const field = Object.keys(err.keyValue)[0];
message = `Bu ${field} değeri zaten kullanılıyor`;
errorCode = 'DUPLICATE_KEY';
}
if (err.name === 'ValidationError' && err.errors) {
statusCode = 400;
message = Object.values(err.errors).map(e => e.message).join(', ');
errorCode = 'VALIDATION_ERROR';
}
// JWT hataları
if (err.name === 'JsonWebTokenError') {
statusCode = 401;
message = 'Geçersiz token';
errorCode = 'INVALID_TOKEN';
}
if (err.name === 'TokenExpiredError') {
statusCode = 401;
message = 'Token süresi dolmuş';
errorCode = 'TOKEN_EXPIRED';
}
// Loglama
if (statusCode >= 500) {
console.error(`[CRITICAL] ${err.message}`, {
stack: err.stack,
url: req.originalUrl,
method: req.method,
ip: req.ip,
userId: req.user?.id
});
} else {
console.warn(`[WARN] ${statusCode} - ${message}`, {
url: req.originalUrl,
method: req.method
});
}
// Response
res.status(statusCode).json({
success: false,
error: {
message,
code: errorCode,
...(process.env.NODE_ENV === 'development' && {
stack: err.stack,
details: err.errors
})
}
});
};
// Express app'e ekle
app.use(errorHandler);
3. Unhandled Error ve Process-Level Hata Yönetimi
Express middleware'inin yakayamadığı hatalar da vardır: unhandled promise rejection'lar ve uncaught exception'lar. Bu hataları process seviyesinde yakalamak, uygulamanın sessizce çökmesini önler ve graceful shutdown sağlar.
// Unhandled Promise Rejection
process.on('unhandledRejection', (reason, promise) => {
console.error('[FATAL] Unhandled Promise Rejection:', {
reason: reason?.message || reason,
stack: reason?.stack
});
// Graceful shutdown
server.close(() => {
console.log('Sunucu kapatıldı (unhandled rejection)');
process.exit(1);
});
// 10 saniye içinde kapanamadıysa zorla kapat
setTimeout(() => {
console.error('Zorla kapatılıyor...');
process.exit(1);
}, 10000);
});
// Uncaught Exception
process.on('uncaughtException', (error) => {
console.error('[FATAL] Uncaught Exception:', {
message: error.message,
stack: error.stack
});
// Uncaught exception sonrası uygulama güvenilmez durumdadır
// Mutlaka yeniden başlatılmalıdır
process.exit(1);
});
// SIGTERM handler (Docker, Kubernetes graceful shutdown)
process.on('SIGTERM', () => {
console.log('SIGTERM alındı. Graceful shutdown başlatılıyor...');
server.close(() => {
console.log('HTTP sunucusu kapatıldı');
// Veritabanı bağlantılarını kapat
mongoose.connection.close(false, () => {
console.log('MongoDB bağlantısı kapatıldı');
process.exit(0);
});
});
});
4. Retry Pattern ve Circuit Breaker
Dış servislere yapılan çağrılarda geçici hatalar (network timeout, 503 Service Unavailable) sıkça karşılaşılan durumlardır. Retry pattern, başarısız istekleri belirli aralıklarla tekrar dener. Circuit breaker ise ardışık hata sayısı bir eşiği aştığında istekleri geçici olarak durdurarak dış servisin toparlanmasına zaman tanır.
// Retry mekanizması (exponential backoff ile)
async function retryOperation(operation, options = {}) {
const { maxRetries = 3, baseDelay = 1000, maxDelay = 10000 } = options;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries) throw error;
// Sadece geçici hatalar için tekrar dene
if (!isRetryable(error)) throw error;
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
const jitter = delay * (0.5 + Math.random() * 0.5);
console.warn(`Deneme ${attempt}/${maxRetries} başarısız. ` +
`${Math.round(jitter)}ms sonra tekrar denenecek...`);
await new Promise(resolve => setTimeout(resolve, jitter));
}
}
}
function isRetryable(error) {
const retryableErrors = ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED'];
const retryableStatuses = [408, 429, 500, 502, 503, 504];
return retryableErrors.includes(error.code) ||
retryableStatuses.includes(error.statusCode);
}
// Kullanım
const data = await retryOperation(
() => fetch('https://api.example.com/data').then(r => r.json()),
{ maxRetries: 3, baseDelay: 1000 }
);
5. Production Error Monitoring
Production ortamında hataları izlemek ve anında haberdar olmak için Sentry, Datadog veya New Relic gibi error monitoring servisleri kullanılmalıdır. Bu araçlar hata sıklığını, etkilenen kullanıcı sayısını, stack trace'leri ve hata trendlerini görselleştirir. Ayrıca alert mekanizmaları ile kritik hatalar anında ekibe bildirilir.
Structured logging ile hatalarınızı JSON formatında kaydedin. Bu sayede log aggregation araçları (ELK Stack, Grafana Loki) ile logları kolayca arayabilir, filtreleyebilir ve analiz edebilirsiniz. Her log kaydına request ID, kullanıcı bilgisi ve context eklemek, hata ayıklama sürecini büyük ölçüde hızlandırır.
Sonuç
Production-ready hata yönetimi, özel hata sınıfları, merkezi error handler, process-level hata yakalama, retry mekanizmaları ve monitoring araçlarının bir bütün olarak uygulanmasını gerektirir. Bu rehberdeki pattern'leri uygulamaya koyarak uygulamanızın güvenilirliğini artırabilir, hataları hızlıca tespit edebilir ve kullanıcılarınıza tutarlı bir deneyim sunabilirsiniz.