İçeriğe geç
Node.js

Node.js Middleware Pattern: İstek İşleme Zinciri

Tarık Tunç
Node.js Middleware Pattern: İstek İşleme Zinciri

Node.js Middleware Pattern: İstek İşleme Zinciri

Middleware pattern, Node.js ve Express.js ekosisteminin en temel tasarım kalıplarından biridir. Bir HTTP isteğinin sunucuya ulaşmasından yanıtın istemciye geri dönmesine kadar olan süreçte, istek birden fazla ara katmandan (middleware) geçer. Her middleware fonksiyonu isteği inceleyebilir, değiştirebilir, yanıt döndürebilir veya bir sonraki middleware'e iletebilir. Bu rehberde middleware pattern'inin çalışma prensiplerini, farklı middleware türlerini ve kendi middleware'lerinizi nasıl yazacağınızı kapsamlı şekilde inceleyeceğiz.

1. Middleware Nedir ve Nasıl Çalışır?

Middleware, request (req), response (res) ve next parametrelerini alan bir fonksiyondur. next() çağrıldığında kontrol bir sonraki middleware'e geçer; çağrılmazsa istek zinciri orada durur. Bu zincirleme yapı, cross-cutting concern'leri (logging, authentication, validation, error handling) modüler ve yeniden kullanılabilir şekilde uygulamanızı sağlar.

const express = require('express');
const app = express();

// 1. Uygulama düzeyinde middleware (tüm route'lara uygulanır)
app.use((req, res, next) => {
  req.requestTime = Date.now();
  req.requestId = crypto.randomUUID();
  console.log(`[${req.requestId}] ${req.method} ${req.url} - Başladı`);
  next();
});

// 2. Belirli path'e özel middleware
app.use('/api', (req, res, next) => {
  const apiKey = req.headers['x-api-key'];
  if (!apiKey || apiKey !== process.env.API_KEY) {
    return res.status(401).json({ error: 'Geçersiz API anahtarı' });
  }
  next();
});

// 3. Route düzeyinde middleware
const logRequest = (req, res, next) => {
  console.log(`Request Body: ${JSON.stringify(req.body)}`);
  next();
};

app.post('/api/users', logRequest, (req, res) => {
  res.status(201).json({ message: 'Kullanıcı oluşturuldu' });
});

// 4. Response time hesaplama (res.on ile)
app.use((req, res, next) => {
  const start = process.hrtime.bigint();
  res.on('finish', () => {
    const end = process.hrtime.bigint();
    const durationMs = Number(end - start) / 1e6;
    console.log(`[${req.requestId}] ${req.method} ${req.url} - ${res.statusCode} - ${durationMs.toFixed(2)}ms`);
  });
  next();
});
Bilgi: Middleware sırası kritiktir. Express, middleware'leri tanımlandıkları sıraya göre çalıştırır. Örneğin, body parser middleware'i route handler'lardan önce tanımlanmalıdır, aksi halde req.body undefined olur.

2. Yaygın Middleware Türleri ve Kullanım Senaryoları

Express uygulamalarında farklı amaçlara hizmet eden çeşitli middleware türleri vardır. Built-in middleware'ler (express.json, express.static), third-party middleware'ler (cors, helmet, morgan) ve custom middleware'ler bir arada kullanılarak güçlü bir istek işleme pipeline'ı oluşturulur.

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');

const app = express();

// === Built-in Middleware ===
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));

// === Güvenlik Middleware'leri ===
app.use(helmet());
app.use(cors({
  origin: ['https://example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true
}));

// === Rate Limiting ===
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 dakika
  max: 100,                    // Maksimum 100 istek
  standardHeaders: true,
  legacyHeaders: false,
  message: {
    error: 'Çok fazla istek gönderildi, lütfen daha sonra tekrar deneyin'
  }
});
app.use('/api', limiter);

// === Response Compression ===
app.use(compression({
  threshold: 1024,  // 1KB üzeri yanıtları sıkıştır
  filter: (req, res) => {
    if (req.headers['x-no-compression']) return false;
    return compression.filter(req, res);
  }
}));

3. Custom Authentication Middleware

Authentication middleware, korunan route'lara erişimi kontrol eder. JWT (JSON Web Token) tabanlı authentication, modern API'lerde en yaygın kullanılan yöntemdir. Middleware, Authorization header'ındaki token'ı doğrulayarak kullanıcı bilgilerini request nesnesine ekler.

const jwt = require('jsonwebtoken');

// Authentication middleware
const authenticate = (req, res, next) => {
  try {
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res.status(401).json({
        success: false,
        message: 'Erişim token\'ı gerekli'
      });
    }

    const token = authHeader.split(' ')[1];
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    req.user = {
      id: decoded.userId,
      email: decoded.email,
      role: decoded.role
    };

    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({
        success: false,
        message: 'Token süresi dolmuş'
      });
    }
    return res.status(403).json({
      success: false,
      message: 'Geçersiz token'
    });
  }
};

// Authorization middleware (rol bazlı erişim kontrolü)
const authorize = (...allowedRoles) => {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ success: false, message: 'Önce giriş yapınız' });
    }
    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({
        success: false,
        message: 'Bu işlem için yetkiniz bulunmamaktadır'
      });
    }
    next();
  };
};

// Kullanım
app.get('/api/profile', authenticate, (req, res) => {
  res.json({ success: true, data: req.user });
});

app.delete('/api/users/:id', authenticate, authorize('admin'), (req, res) => {
  res.json({ success: true, message: 'Kullanıcı silindi' });
});
Uyarı: JWT secret'ınızı asla kaynak koduna yazmayın; environment variable olarak saklayın. Token sürelerini makul tutun (access token: 15-60 dakika, refresh token: 7-30 gün). Logout işlemi için token blacklist mekanizması kullanmayı düşünün.

4. Error Handling Middleware

Express'te hata yakalama middleware'leri dört parametre alır: err, req, res, next. Bu middleware'ler, uygulamadaki tüm hataları merkezi bir noktada yakalar ve tutarlı hata yanıtları döndürür. Farklı hata türleri için farklı HTTP status kodları ve mesajları kullanılmalıdır.

// Özel hata sınıfı
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

// Async handler wrapper - try/catch tekrarını önler
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Route'larda kullanım
app.get('/api/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) {
    throw new AppError('Kullanıcı bulunamadı', 404);
  }
  res.json({ success: true, data: user });
}));

// Global error handler (en son tanımlanmalı)
app.use((err, req, res, next) => {
  err.statusCode = err.statusCode || 500;

  // Mongoose CastError (geçersiz ObjectId)
  if (err.name === 'CastError') {
    err.message = 'Geçersiz kaynak ID formatı';
    err.statusCode = 400;
  }

  // Mongoose Duplicate Key
  if (err.code === 11000) {
    const field = Object.keys(err.keyValue)[0];
    err.message = `Bu ${field} değeri zaten kullanılıyor`;
    err.statusCode = 409;
  }

  // Mongoose Validation Error
  if (err.name === 'ValidationError') {
    const messages = Object.values(err.errors).map(e => e.message);
    err.message = messages.join(', ');
    err.statusCode = 400;
  }

  console.error(`[ERROR] ${err.statusCode} - ${err.message}`);

  res.status(err.statusCode).json({
    success: false,
    message: err.isOperational ? err.message : 'Sunucu hatası oluştu',
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
});
İpucu: asyncHandler wrapper fonksiyonu, her route handler'da try/catch yazmak yerine hataları otomatik olarak error handling middleware'ine iletir. Bu pattern, kod tekrarını önemli ölçüde azaltır ve kodun okunabilirliğini artırır.

5. Middleware Composition ve Chaining

Birden fazla middleware'i birleştirerek karmaşık istek işleme pipeline'ları oluşturabilirsiniz. Bu yaklaşım, her middleware'in tek bir sorumluluğu olmasını sağlar (Single Responsibility Principle) ve middleware'lerin farklı kombinasyonlarda yeniden kullanılabilmesine olanak tanır.

Middleware'leri compose ederken sıralamaya dikkat etmek gerekir. Genel olarak şu sıralama önerilir: security middleware'ler (helmet, cors), parsing middleware'ler (json, urlencoded), logging middleware'ler (morgan), authentication middleware'ler, route handler'lar ve en son error handling middleware. Bu sıralama, her katmanın doğru çalışmasını ve güvenlik açıklarının önlenmesini sağlar.

Sonuç

Middleware pattern, Node.js uygulamalarının temel yapı taşıdır. Authentication, validation, logging, error handling ve rate limiting gibi cross-cutting concern'leri modüler ve yeniden kullanılabilir şekilde uygulamanızı sağlar. Bu rehberde ele aldığımız pattern'ler ve best practice'ler, production-ready bir Express.js uygulaması oluşturmanın temelini oluşturur. Middleware'lerinizi küçük, tek sorumluluğa sahip ve test edilebilir şekilde tasarlamak uzun vadede projenizin sürdürülebilirliğini artıracaktır.

Kaynaklar

İlgili Yazılar