Takip et

Node.js HTTP Sunucusu ile Kendi Modüler HTTP Uygulamanızı Geliştirme Rehberi

Node.js, asenkron ve olay tabanlı mimarisi sayesinde sunucu tarafı uygulamalar geliştirmek için popüler bir platform haline gelmiştir. Genellikle Expre…

Node.js HTTP Sunucusu ile Kendi Modüler HTTP Uygulamanızı Geliştirme Rehberi

Node.js, asenkron ve olay tabanlı mimarisi sayesinde sunucu tarafı uygulamalar geliştirmek için popüler bir platform haline gelmiştir. Genellikle Express.js gibi framework’ler aracılığıyla web uygulamaları geliştirilse de, Node.js’in kendi bünyesindeki http modülü, HTTP sunucularını en temel seviyede anlamak ve kontrol etmek için güçlü bir temel sunar. Bu makale, Node.js’in yerleşik HTTP sunucusunu kullanarak nasıl modüler ve ölçeklenebilir bir HTTP uygulaması oluşturabileceğinizi adım adım açıklayacaktır. Kendi HTTP modülünüzü inşa etmek, framework’lerin arkasındaki mekanizmaları kavramanıza ve daha optimize edilmiş, ihtiyaca özel çözümler geliştirmenize olanak tanır.

Node.js HTTP Sunucusuna Derinlemesine Bakış

Node.js, JavaScript’i sunucu tarafında çalıştırmamızı sağlayan bir çalışma zamanı ortamıdır. Web uygulamalarının temelini oluşturan HTTP protokolü ile etkileşim kurmak için güçlü ve esnek bir http modülü sunar. Bu bölüm, bu modülün temel bileşenlerini ve nasıl çalıştığını detaylandıracaktır.

http Modülünün Temelleri

http modülü, Node.js’in çekirdek modüllerinden biridir ve herhangi bir ek kurulum gerektirmez. Bu modül, HTTP istemcisi ve sunucusu işlevselliği sağlar. Bir web sunucusu oluşturmak için genellikle http.createServer() metodu kullanılırken, harici bir API’ye istek göndermek için http.request() veya http.get() metotları tercih edilir. Kendi HTTP modülümüzü oluştururken, bu temel işlevsellikleri kullanarak isteklere yanıt vereceğiz ve sunucumuzun davranışını özelleştireceğiz.

createServer Metodu ve Olay Tabanlı Mimari

http.createServer() metodu, bir HTTP sunucusu nesnesi döndürür. Bu metoda genellikle bir geri çağırma (callback) fonksiyonu argüman olarak verilir. Bu geri çağırma fonksiyonu, sunucuya her yeni istek geldiğinde çalıştırılır ve iki ana parametre alır: request (gelen istek) ve response (gönderilecek yanıt). Node.js’in olay tabanlı mimarisi sayesinde, bu sunucu nesnesi çeşitli olayları (örneğin, 'request', 'connection', 'close') dinleyebilir ve bu olaylar tetiklendiğinde belirli fonksiyonları çalıştırabilir.


const http = require('http');

const server = http.createServer((req, res) => {
  // Her istek geldiğinde bu fonksiyon çalışır
  console.log(Gelen İstek: ${req.method} ${req.url});
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Merhaba Dunya!\n');
});

const PORT = 3000;
server.listen(PORT, () => {
  console.log(Sunucu http://localhost:${PORT} adresinde çalışıyor);
});

request ve response Objelerinin Anatomisi

Geri çağırma fonksiyonundaki req (IncomingMessage) ve res (ServerResponse) objeleri, gelen isteği işlemek ve yanıtı oluşturmak için kritik öneme sahiptir.

  • request (IncomingMessage) Objesi: Gelen HTTP isteği hakkında tüm bilgileri içerir. Önemli özellikleri arasında req.url (isteğin URL'si), req.method (HTTP metodu: GET, POST vb.), req.headers (istek başlıkları) ve req.body (istek gövdesi, akış olarak okunur) bulunur.
  • response (ServerResponse) Objesi: İstemciye geri gönderilecek yanıtı oluşturmak için kullanılır. Önemli metotları arasında res.statusCode (HTTP durum kodu), res.setHeader() (yanıt başlığı ayarlama) ve res.end() (yanıtı sonlandırma ve gönderme) yer alır.

Basit Bir HTTP Modülü Yapılandırma

Şimdi temel bilgileri edindiğimize göre, Node.js'in http modülünü kullanarak basit bir HTTP modülü oluşturmaya başlayabiliriz. Bu bölüm, temel bir sunucu kurulumundan, gelen istekleri işlemeye ve URL ile HTTP metotlarını kullanmaya kadar olan adımları kapsayacaktır.

Temel Sunucu Oluşturma ve Dinleme

Yukarıdaki örnekte gösterildiği gibi, http.createServer() ile bir sunucu oluşturup server.listen() ile belirli bir portta dinlemeye başlamak, her Node.js HTTP uygulamasının başlangıç noktasıdır. Sunucuyu dinlemeye almak, gelen bağlantıları kabul etmeye hazır hale getirmek anlamına gelir. Genellikle localhost (127.0.0.1) ve belirli bir port (örneğin 3000 veya 8080) kullanılır.

İstekleri İşleme ve Yanıt Gönderme

Sunucuya gelen her istek, createServer metoduna verilen geri çağırma fonksiyonu tarafından işlenir. Bu fonksiyon içinde, request objesinden gelen bilgileri okuyarak (URL, metot, başlıklar vb.) ve response objesini kullanarak uygun bir yanıt oluştururuz. Yanıtın içeriğini (Content-Type) ve durum kodunu (statusCode) ayarlamak önemlidir.


// ... (http modülü ve server.listen kısmı)

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html');
    res.end('

Ana Sayfa

Hoş geldiniz!

'); } else if (req.url === '/hakkinda') { res.statusCode = 200; res.setHeader('Content-Type', 'text/html'); res.end('

Hakkımızda

Bu bir Node.js HTTP modülüdür.

'); } else { res.statusCode = 404; res.setHeader('Content-Type', 'text/plain'); res.end('404 Sayfa Bulunamadı'); } });

URL ve HTTP Metotlarını Kullanma

req.url ve req.method, gelen isteği farklı yollarla işlemek için en temel araçlardır. req.url, isteğin yolunu (path) ve sorgu dizgisini (query string) içerir. req.method ise isteğin HTTP metodunu (GET, POST, PUT, DELETE vb.) belirtir. Bu bilgilerle, uygulamanızın farklı URL'lere ve farklı HTTP metotlarına nasıl yanıt vereceğini belirleyebilirsiniz. Örneğin, /api/users adresine gelen bir GET isteği kullanıcı listesini döndürürken, aynı adrese gelen bir POST isteği yeni bir kullanıcı oluşturabilir.

Gelişmiş İstek Yönlendirme (Routing) Mekanizmaları

Uygulamalar büyüdükçe, her URL'yi ve metodu if-else bloklarıyla yönetmek sürdürülemez hale gelir. Bu noktada, istek yönlendirme (routing) mekanizmalarına ihtiyaç duyarız. Bu bölüm, kendi basit yönlendirme çözümümüzü nasıl oluşturacağımızı ve parametreli URL'leri nasıl işleyeceğimizi anlatır.

Manuel Yönlendirme Yaklaşımları

Yukarıdaki örnekte gösterilen if-else zinciri, manuel yönlendirmeye bir örnektir. Daha karmaşık senaryolarda, bu yapıyı daha düzenli hale getirmek için bir dizi obje veya fonksiyon kullanabiliriz. Örneğin, her bir rotayı (route) bir obje olarak tanımlayıp, bu objeleri bir dizide tutarak istek geldiğinde uygun rotayı arayabiliriz.

Basit Bir Router Modülü Oluşturma

Uygulamanın ana dosyasını temiz tutmak için, yönlendirme mantığını ayrı bir modüle taşımak iyi bir pratiktir. Bu modül, gelen isteğin URL'sini ve metodunu alarak, uygun işleyici fonksiyonu (handler) bulur ve çalıştırır.


// router.js
const routes = {
  'GET /': (req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html');
    res.end('

Ana Sayfa

'); }, 'GET /api/users': (req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify([{ id: 1, name: 'Ali' }, { id: 2, name: 'Veli' }])); }, 'POST /api/users': (req, res) => { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { const newUser = JSON.parse(body); res.statusCode = 201; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ message: 'Kullanıcı oluşturuldu', user: newUser })); }); } }; function handleRoute(req, res) { const routeKey = ${req.method} ${req.url}; const handler = routes[routeKey]; if (handler) { handler(req, res); } else { res.statusCode = 404; res.setHeader('Content-Type', 'text/plain'); res.end('404 Sayfa Bulunamadı'); } } module.exports = handleRoute; // server.js const http = require('http'); const router = require('./router'); // Kendi router modülümüzü import ediyoruz const server = http.createServer(router); // router fonksiyonunu doğrudan callback olarak veriyoruz const PORT = 3000; server.listen(PORT, () => { console.log(Sunucu http://localhost:${PORT} adresinde çalışıyor); });

Parametreli URL'ler ve Sorgu Dizileri

Gerçek dünya uygulamalarında URL'ler genellikle parametreler içerir (örn: /users/123). Kendi router'ımızda bu tür parametreleri yakalamak için URL ayrıştırma (parsing) işlemleri yapmamız gerekir. Node.js'in url modülü bu konuda yardımcı olabilir. Ayrıca, req.url içinde yer alan sorgu dizgilerini (?name=Ali&age=30) ayrıştırmak için URLSearchParams API'si veya querystring modülü kullanılabilir.


// router.js (güncellenmiş)
const url = require('url');

const routes = {
  // ... diğer rotalar
  'GET /api/users/:id': (req, res) => {
    const parsedUrl = url.parse(req.url, true);
    const pathSegments = parsedUrl.pathname.split('/').filter(Boolean); // ['api', 'users', '123']
    const userId = pathSegments[2]; // '123'

    // userId ile ilgili işlemi yap
    res.statusCode = 200;
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ id: userId, name: Kullanıcı ${userId} }));
  }
};

function handleRoute(req, res) {
  const parsedUrl = url.parse(req.url, true);
  const path = parsedUrl.pathname;
  const method = req.method;

  // Dinamik rotaları kontrol etmek için daha karmaşık bir mantık
  for (const routeKey in routes) {
    const [routeMethod, routePath] = routeKey.split(' ');
    if (routeMethod === method) {
      // Basit bir regex veya string karşılaştırma ile parametreleri yakalama
      const routeRegex = new RegExp(^${routePath.replace(/:[a-zA-Z0-9_]+/g, '([a-zA-Z0-9_]+)')}$);
      const match = path.match(routeRegex);
      if (match) {
        // Parametreleri req objesine ekleyebiliriz
        // req.params = { id: match[1] }; // Örnek
        routes[routeKey](req, res);
        return;
      }
    }
  }

  // Eğer eşleşen rota bulunamazsa
  res.statusCode = 404;
  res.setHeader('Content-Type', 'text/plain');
  res.end('404 Sayfa Bulunamadı');
}
// ...

Middleware Kavramı ve Uygulaması

Middleware (ara yazılım), bir isteğin sunucuya ulaşmasından ve bir yanıtın istemciye gönderilmesinden önce veya sonra belirli işlemleri gerçekleştiren fonksiyonlardır. Kimlik doğrulama, loglama, veri ayrıştırma gibi görevler için çok kullanışlıdırlar. Express.js gibi framework'lerde yaygın olarak kullanılırken, kendi HTTP modülümüzde de benzer bir yapıyı oluşturabiliriz.

Middleware Nedir ve Neden Kullanılır?

Middleware fonksiyonları, gelen isteği işleyen ana işleyiciye ulaşmadan önce veya sonra çalışır. Her middleware fonksiyonu, isteği işleyebilir, yanıtı değiştirebilir ve isteği bir sonraki middleware'e veya nihai işleyiciye iletebilir. Bu, kod tekrarını azaltır, modülerliği artırır ve uygulamanın farklı katmanlarında ortak işlevselliği kolayca yönetmeyi sağlar.

  • Loglama: Her isteği kaydetmek.
  • Kimlik Doğrulama: Kullanıcının yetkili olup olmadığını kontrol etmek.
  • Vücut Ayrıştırma (Body Parsing): POST/PUT isteklerindeki JSON veya form verilerini ayrıştırmak.
  • Hata Yönetimi: Uygulama genelinde hataları yakalamak.

Kendi Basit Middleware Fonksiyonlarınızı Yazma

Bir middleware fonksiyonu genellikle (req, res, next) parametrelerini alır. req ve res bilinen istek ve yanıt objeleridir. next ise, bir sonraki middleware fonksiyonunu veya nihai rota işleyicisini çağırmak için kullanılan bir fonksiyondur. next() çağrılmazsa, istek zinciri durur.


// loggerMiddleware.js
function loggerMiddleware(req, res, next) {
  console.log([${new Date().toISOString()}] ${req.method} ${req.url});
  next(); // Bir sonraki middleware'e veya rota işleyicisine geç
}
module.exports = loggerMiddleware;

// authMiddleware.js
function authMiddleware(req, res, next) {
  const token = req.headers.authorization;
  if (token === 'Bearer mysecrettoken') {
    // req.user = { id: 1, role: 'admin' }; // Kullanıcı bilgilerini ekleyebiliriz
    next();
  } else {
    res.statusCode = 401;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Yetkisiz Erişim');
  }
}
module.exports = authMiddleware;

Middleware Zincirleri Oluşturma

Birden fazla middleware'i sırayla çalıştırmak için bir zincir oluşturabiliriz. Ana sunucu fonksiyonumuzda, her isteği bu middleware zincirinden geçirebiliriz.


// app.js (ana uygulama dosyası)
const http = require('http');
const url = require('url');
const loggerMiddleware = require('./loggerMiddleware');
const authMiddleware = require('./authMiddleware');

// Basit bir middleware yöneticisi
const middlewares = [];

function use(middleware) {
  middlewares.push(middleware);
}

// Middleware'leri tanımla
use(loggerMiddleware);
// use(authMiddleware); // İsteğe bağlı olarak kimlik doğrulama ekle

const routes = {
  'GET /': (req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html');
    res.end('

Ana Sayfa

'); }, 'GET /protected': (req, res) => { // Bu rotaya erişim için authMiddleware'den geçmek gerekir res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Korumalı Alan!'); } }; const server = http.createServer((req, res) => { let middlewareIndex = 0; const next = () => { if (middlewareIndex < middlewares.length) { middlewares[middlewareIndex++](req, res, next); } else { // Tüm middleware'ler tamamlandı, şimdi rotayı işle const parsedUrl = url.parse(req.url, true); const routeKey = ${req.method} ${parsedUrl.pathname}; const handler = routes[routeKey]; if (handler) { handler(req, res); } else { res.statusCode = 404; res.setHeader('Content-Type', 'text/plain'); res.end('404 Sayfa Bulunamadı'); } } }; next(); // Middleware zincirini başlat }); const PORT = 3000; server.listen(PORT, () => { console.log(Sunucu http://localhost:${PORT} adresinde çalışıyor); });

Modüler Yapıyı Güçlendirme ve Bakım

Büyük uygulamalarda tüm kodun tek bir dosyada olması yönetilemez hale gelir. Node.js'in modül sistemi (CommonJS), kodu ayrı dosyalara bölerek daha düzenli, okunabilir ve bakımı kolay bir yapı oluşturmamızı sağlar.

Kodun Modüllere Ayrılması

Her bir işlevselliği (örneğin, router, middleware, veritabanı işlemleri, yardımcı fonksiyonlar) kendi ayrı dosyasına taşımak en iyi yaklaşımdır. Her dosya, module.exports kullanarak dışarıya bir fonksiyon, obje veya sınıf ihraç eder. Diğer dosyalar ise require() fonksiyonu ile bu ihraç edilen modülleri içe aktarır.

Örneğin, routes.js, middlewares.js, controllers.js gibi dosyalar oluşturarak kodu daha iyi organize edebiliriz. Bu, her bir modülün belirli bir sorumluluğa sahip olmasını sağlar ve uygulamanın genel mimarisini basitleştirir.

Farklı Modülleri Birleştirme

Ana uygulama dosyanız (genellikle app.js veya server.js), tüm bu modülleri bir araya getiren orkestratör görevi görür. Burada middleware'ler uygulanır, rotalar tanımlanır ve sunucu başlatılır. Bu yapı, her bir bileşenin bağımsız olarak geliştirilip test edilmesine olanak tanır.

Yapısal En İyi Uygulamalar

  • Sorumlulukların Ayrımı (Separation of Concerns): Her modülün tek bir işi yapmasını sağlayın.
  • Tutarlı İsimlendirme: Dosya ve değişken isimlerinde tutarlılık kullanın.
  • Dizin Yapısı: Modülleri mantıksal olarak gruplandıran bir dizin yapısı oluşturun (örn: /routes, /middlewares, /controllers, /utils).
  • Küçük Modüller: Mümkün olduğunca küçük ve odaklanmış modüller yazın.

Hata Yönetimi, Güvenlik ve Performans İpuçları

Sağlam bir HTTP uygulaması sadece işlevsel olmakla kalmaz, aynı zamanda hataları düzgün bir şekilde yönetmeli, güvenlik açıklarına karşı korunmalı ve iyi bir performans sunmalıdır.

Sağlam Hata Yönetimi Stratejileri

Node.js'te hatalar asenkron doğaları nedeniyle bazen zorlayıcı olabilir. Sunucu tarafında oluşabilecek hataları (örn: dosya okuma hatası, veritabanı bağlantı hatası) yakalamak ve istemciye uygun bir hata yanıtı göndermek önemlidir.

  • try-catch Blokları: Senkron kod bloklarındaki hataları yakalamak için kullanılır.
  • Promise .catch(): Promise tabanlı asenkron işlemlerin hatalarını yönetmek için kullanılır.
  • Global Hata Yakalayıcılar: Yakalanmayan hatalar için process.on('uncaughtException') ve process.on('unhandledRejection') gibi olay dinleyicileri kullanılabilir. Bu, uygulamanın çökmesini önlemek ve temiz bir şekilde kapanmasını sağlamak için önemlidir.
  • Özel Hata Middleware'i: Tüm hata durumlarını merkezi bir noktada ele almak için özel bir hata middleware'i oluşturulabilir.

Temel Güvenlik Önlemleri

Kendi HTTP modülünüzü geliştirirken temel güvenlik adımlarını göz ardı etmemelisiniz:

  • Giriş Doğrulama (Input Validation): Kullanıcıdan gelen tüm girişleri her zaman doğrulayın ve sanitize edin.
  • HTTP Başlıkları: Güvenlik ile ilgili HTTP başlıklarını (örn: X-Content-Type-Options, X-Frame-Options, Content-Security-Policy) ayarlayın.
  • Hassas Veri Yönetimi: Parolalar gibi hassas verileri şifreleyin ve güvenli bir şekilde saklayın.
  • Rate Limiting: DDoS saldırılarını veya brute-force denemelerini önlemek için istek hız sınırlaması uygulayın.
  • Güvenlik Açıkları: SQL Enjeksiyonu, XSS (Cross-Site Scripting), CSRF (Cross-Site Request Forgery) gibi yaygın güvenlik açıklarına karşı önlemler alın.

Performans İyileştirmeleri ve Ölçeklenebilirlik

Node.js, tek iş parçacıklı yapısı nedeniyle CPU yoğun işlemler için doğrudan uygun değildir. Ancak doğru stratejilerle yüksek performanslı ve ölçeklenebilir uygulamalar geliştirebilirsiniz:

  • Asenkron İşlemler: Blocking (engelleyici) operasyonlardan kaçının. Tüm I/O (giriş/çıkış) işlemlerini asenkron olarak yapın.
  • Kümeleme (Clustering): Node.js'in cluster modülünü kullanarak birden fazla CPU çekirdeğini kullanabilir ve uygulamanızın performansını artırabilirsiniz.
  • Yük Dengeleme (Load Balancing): Birden fazla uygulama örneğini bir yük dengeleyici arkasına yerleştirerek gelen istekleri dağıtın.
  • Önbellekleme (Caching): Sık erişilen verileri önbelleğe alarak veritabanı veya diğer kaynaklara yapılan gereksiz çağrıları azaltın.
  • Statik Dosya Sunumu: Resimler, CSS, JavaScript gibi statik dosyaları doğrudan Node.js sunucusu yerine Nginx gibi optimize edilmiş bir web sunucusu üzerinden sunun.

Sonuç

Bu makalede, Node.js'in yerleşik http modülünü kullanarak nasıl modüler bir HTTP uygulaması oluşturulacağını detaylı bir şekilde inceledik. Temel sunucu kurulumundan, istek yönlendirmeye, middleware kullanımına ve modüler yapılandırmaya kadar birçok konuyu ele aldık. Kendi HTTP modülünüzü inşa etmek, Node.js'in altında yatan mekanizmaları daha iyi anlamanızı sağlar ve size framework'lerin sunduğu kolaylıkların ötesinde daha fazla kontrol ve esneklik sunar. Bu yaklaşım, performans kritik uygulamalar veya öğrenme amaçlı projeler için paha biçilmez bir deneyim sağlar. Temel bilgileri ve en iyi uygulamaları takip ederek, sağlam, güvenli ve ölçeklenebilir Node.js HTTP uygulamaları geliştirebilirsiniz.

SSS (Sık Sorulan Sorular)

Neden Express.js yerine kendi HTTP modülümüzü yazmalıyız?

Kendi HTTP modülünüzü yazmak, Node.js'in temel HTTP mekanizmalarını derinlemesine anlamanızı sağlar. Bu, framework'lerin nasıl çalıştığını kavramanıza yardımcı olur ve özel ihtiyaçlarınıza göre daha hafif, optimize edilmiş çözümler geliştirmenize olanak tanır. Ayrıca, framework bağımlılığını azaltır ve daha fazla kontrol sağlar.

Bu yaklaşımın dezavantajları nelerdir?

En büyük dezavantajı, Express.js gibi olgun framework'lerin sunduğu birçok hazır özelliği (örneğin, gelişmiş yönlendirme,

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.