Node.js ile Basit HTTP Sunucusu Kurulumu

Bir web uygulaması geliştiriyorsunuz ya da sadece bir şeyler denemek istiyorsunuz, her iki durumda da Node.js ile HTTP sunucusu kurmak öğrenmesi gereken en temel becerilerden biri. Konuya “merhaba dünya” seviyesinde değil, gerçekten işe yarar bir şeyler yapabileceğiniz noktadan bakacağız. Sıfırdan başlayıp production ortamında kullanılabilecek bir yapıya kadar gideceğiz.

Node.js Kurulumu

Önce temelden başlayalım. Node.js kurulumu için doğrudan resmi siteden indirme yapabilirsiniz ama benim tavsiyem nvm (Node Version Manager) kullanmak. Neden? Çünkü farklı projeler farklı Node.js sürümleri gerektirebilir ve nvm ile bu geçişi saniyeler içinde yapabilirsiniz.

# nvm kurulumu
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# Terminal yeniden başlatılır ya da şu komut çalıştırılır
source ~/.bashrc

# Node.js LTS sürümünü kur
nvm install --lts

# Kurulu sürümü kontrol et
node --version
npm --version

Ubuntu veya Debian tabanlı sistemlerde doğrudan paket yöneticisi ile de kurabilirsiniz ama bu yöntemle genellikle eski sürümler gelir. nvm tercih edin derim.

Windows kullanıyorsanız nvm-windows aracını kullanabilirsiniz ya da doğrudan nodejs.org adresinden MSI paketini indirip kurabilirsiniz. WSL2 üzerinde çalışıyorsanız Linux kurulum adımlarını takip edin, çok daha temiz bir deneyim sunar.

İlk HTTP Sunucusu: Core http Modülü

Node.js’in dahili http modülü ile hiçbir dış bağımlılık olmadan bir HTTP sunucusu ayağa kaldırabilirsiniz. Bu temeli anlamak önemli çünkü Express gibi framework’ler de altında bu modülü kullanıyor.

mkdir http-sunucu-demo
cd http-sunucu-demo

Şimdi sunucu.js adında bir dosya oluşturun:

cat > sunucu.js << 'EOF'
const http = require('http');

const HOST = 'localhost';
const PORT = 3000;

const sunucu = http.createServer((istek, yanit) => {
  // Gelen isteğin URL ve metodunu logla
  console.log(`[${new Date().toISOString()}] ${istek.method} ${istek.url}`);

  // Yanit başlıklarını ayarla
  yanit.writeHead(200, {
    'Content-Type': 'text/plain; charset=utf-8',
    'X-Powered-By': 'Node.js'
  });

  yanit.end('Merhaba! Sunucu çalışıyor.n');
});

sunucu.listen(PORT, HOST, () => {
  console.log(`Sunucu http://${HOST}:${PORT} adresinde çalışıyor`);
});

// Hata yakalama
sunucu.on('error', (hata) => {
  if (hata.code === 'EADDRINUSE') {
    console.error(`Port ${PORT} zaten kullanımda!`);
    process.exit(1);
  }
  throw hata;
});
EOF

Sunucuyu başlatın:

node sunucu.js

Başka bir terminal açıp test edin:

curl -i http://localhost:3000

Gayet güzel. Ama bu sunucu her isteğe aynı yanıtı veriyor. Gerçek dünyada URL’ye göre farklı içerik döndürmeniz gerekiyor.

URL Routing: Manuel Yönlendirme

Basit bir routing mekanizması ekleyelim. Büyük projelerde Express kullanmak daha mantıklı olsa da küçük araçlar için bu yaklaşım oldukça işe yarıyor.

cat > router.js << 'EOF'
const http = require('http');
const url = require('url');

const PORT = 3000;

// Rotaları tanımla
const rotalar = {
  'GET /': (istek, yanit) => {
    yanit.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
    yanit.end(JSON.stringify({
      mesaj: 'Ana sayfa',
      zaman: new Date().toISOString()
    }));
  },

  'GET /saglik': (istek, yanit) => {
    yanit.writeHead(200, { 'Content-Type': 'application/json' });
    yanit.end(JSON.stringify({
      durum: 'aktif',
      uptime: process.uptime(),
      bellek: process.memoryUsage()
    }));
  },

  'GET /hakkinda': (istek, yanit) => {
    yanit.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    yanit.end('Bu bir Node.js demo sunucusudur.n');
  }
};

const sunucu = http.createServer((istek, yanit) => {
  const parcalanmisUrl = url.parse(istek.url);
  const rota = `${istek.method} ${parcalanmisUrl.pathname}`;

  console.log(`[${new Date().toISOString()}] ${rota}`);

  if (rotalar[rota]) {
    rotalar[rota](istek, yanit);
  } else {
    yanit.writeHead(404, { 'Content-Type': 'application/json' });
    yanit.end(JSON.stringify({ hata: 'Sayfa bulunamadı', kod: 404 }));
  }
});

sunucu.listen(PORT, () => {
  console.log(`Router sunucusu port ${PORT}'de çalışıyor`);
});
EOF

node router.js

Test edelim:

# Ana sayfa
curl http://localhost:3000/

# Sağlık kontrolü
curl http://localhost:3000/saglik

# Olmayan bir sayfa
curl http://localhost:3000/olmayan

Express ile Profesyonel Kurulum

Artık ciddi bir iş yapıyorsanız Express kullanmanız şart. npm ile projeyi başlatıp Express kuralım.

# Yeni proje dizini
mkdir express-uygulama
cd express-uygulama

# package.json oluştur
npm init -y

# Express kur
npm install express

# Geliştirme için nodemon kur
npm install --save-dev nodemon

package.json içindeki scripts bölümünü düzenleyin:

cat > package.json << 'EOF'
{
  "name": "express-uygulama",
  "version": "1.0.0",
  "description": "Express ile HTTP sunucusu",
  "main": "app.js",
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}
EOF

Şimdi app.js dosyasını oluşturalım:

cat > app.js << 'EOF'
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// Middleware'ler
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// İstek loglama middleware
app.use((req, res, next) => {
  const baslangic = Date.now();
  res.on('finish', () => {
    const sure = Date.now() - baslangic;
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} - ${res.statusCode} (${sure}ms)`);
  });
  next();
});

// Rotalar
app.get('/', (req, res) => {
  res.json({
    mesaj: 'Express sunucusu çalışıyor',
    versiyon: '1.0.0',
    zaman: new Date().toISOString()
  });
});

app.get('/saglik', (req, res) => {
  res.json({
    durum: 'OK',
    uptime: `${Math.floor(process.uptime())} saniye`,
    bellek: {
      kullanilan: `${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)} MB`,
      toplam: `${Math.round(process.memoryUsage().heapTotal / 1024 / 1024)} MB`
    }
  });
});

// POST örneği
app.post('/veri', (req, res) => {
  const { ad, email } = req.body;

  if (!ad || !email) {
    return res.status(400).json({
      hata: 'Ad ve email alanları zorunludur'
    });
  }

  res.status(201).json({
    mesaj: 'Veri alındı',
    veri: { ad, email }
  });
});

// 404 handler
app.use((req, res) => {
  res.status(404).json({
    hata: 'Endpoint bulunamadı',
    istenenYol: req.url
  });
});

// Genel hata handler
app.use((err, req, res, next) => {
  console.error('Sunucu hatası:', err.message);
  res.status(500).json({
    hata: 'Sunucu hatası oluştu'
  });
});

app.listen(PORT, () => {
  console.log(`Express sunucusu http://localhost:${PORT} adresinde çalışıyor`);
  console.log(`Ortam: ${process.env.NODE_ENV || 'development'}`);
});

module.exports = app;
EOF

npm run dev

Statik Dosya Sunumu

Birçok projede HTML, CSS, JavaScript dosyalarını da sunmanız gerekir. Express bunu çok kolaylaştırıyor.

# Statik dosyalar için dizin oluştur
mkdir -p public/css public/js

cat > public/index.html << 'EOF'
<!DOCTYPE html>
<html lang="tr">
<head>
  <meta charset="UTF-8">
  <title>Node.js Sunucu</title>
  <link rel="stylesheet" href="/css/stil.css">
</head>
<body>
  <h1>Node.js HTTP Sunucusu Çalışıyor!</h1>
  <div id="durum"></div>
  <script src="/js/uygulama.js"></script>
</body>
</html>
EOF

cat > public/css/stil.css << 'EOF'
body {
  font-family: Arial, sans-serif;
  max-width: 800px;
  margin: 50px auto;
  padding: 20px;
  background: #f5f5f5;
}
h1 { color: #333; }
#durum { padding: 15px; background: #e8f5e9; border-radius: 5px; }
EOF

cat > public/js/uygulama.js << 'EOF'
fetch('/saglik')
  .then(r => r.json())
  .then(veri => {
    document.getElementById('durum').innerHTML =
      `Durum: ${veri.durum} | Uptime: ${veri.uptime}`;
  });
EOF

app.js dosyasına statik dosya middleware ekleyin. app.use(express.json()) satırından sonra şunu ekleyin:

# app.js'e statik dosya desteği ekle
sed -i "/app.use(express.json())/a app.use(express.static('public'));" app.js

Artık tarayıcıdan http://localhost:3000 adresine girdiğinizde HTML sayfasını göreceksiniz.

Ortam Değişkenleri ile Konfigürasyon

Hardcoded değerler production’da büyük sorun çıkarır. .env dosyası ile konfigürasyonu yönetelim.

npm install dotenv
cat > .env << 'EOF'
PORT=3000
NODE_ENV=development
APP_ADI=Demo Sunucu
LOG_SEVIYESI=debug
EOF

cat > .gitignore << 'EOF'
node_modules/
.env
*.log
EOF

app.js‘in en üstüne şunu ekleyin:

# app.js başına dotenv ekle
sed -i '1s/^/require("dotenv").config();n/' app.js

Artık uygulama içinde process.env.APP_ADI gibi değişkenlere erişebilirsiniz. Production’da bu değerleri sunucu ortam değişkeni olarak tanımlarsınız, .env dosyasını asla versiyona koymaz ve asla production sunucusuna yüklemezsiniz.

PM2 ile Production’da Çalıştırma

Geliştirme ortamında node app.js yeterli. Ama production’da sunucu çöktüğünde otomatik yeniden başlamasını, log yönetimini ve cluster modunu istiyorsunuz. PM2 tam bu iş için var.

# PM2 global kur
npm install -g pm2

# Uygulamayı başlat
pm2 start app.js --name "http-sunucu"

# Durum kontrol
pm2 status

# Logları takip et
pm2 logs http-sunucu

# Sunucu yeniden başladığında PM2'nin de başlaması için
pm2 startup
pm2 save

PM2 ile cluster modu kullanarak tüm CPU çekirdeklerini kullanabilirsiniz:

# Tüm CPU çekirdeklerini kullan
pm2 start app.js --name "http-sunucu" -i max

# Ya da belirli sayıda instance
pm2 start app.js --name "http-sunucu" -i 4

# Uygulamayı yeniden başlat
pm2 restart http-sunucu

# Sıfır downtime ile yeniden yükle
pm2 reload http-sunucu

Bir ecosystem.config.js dosyası oluşturmak daha temiz bir yaklaşım:

cat > ecosystem.config.js << 'EOF'
module.exports = {
  apps: [{
    name: 'http-sunucu',
    script: 'app.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'development',
      PORT: 3000
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8080
    },
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    error_file: './logs/hata.log',
    out_file: './logs/cikti.log',
    max_memory_restart: '500M'
  }]
};
EOF

mkdir -p logs

# Development ortamında başlat
pm2 start ecosystem.config.js

# Production ortamında başlat
pm2 start ecosystem.config.js --env production

Nginx ile Ters Proxy Kurulumu

Node.js’i doğrudan 80 veya 443 portunda çalıştırmak yerine Nginx’i ters proxy olarak kullanmak hem güvenli hem de performans açısından daha iyi.

# Nginx kurulumu (Ubuntu/Debian)
sudo apt update && sudo apt install nginx -y

# Yeni site konfigürasyonu oluştur
sudo nano /etc/nginx/sites-available/nodejs-uygulama

Konfigürasyon içeriği:

cat > /tmp/nginx-nodejs.conf << 'EOF'
server {
    listen 80;
    server_name example.com www.example.com;

    # Gzip sıkıştırma
    gzip on;
    gzip_types text/plain application/json application/javascript text/css;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;

        # Zaman aşımı ayarları
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Statik dosyalar için Nginx'i kullan
    location /static/ {
        alias /var/www/nodejs-uygulama/public/;
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }
}
EOF

sudo cp /tmp/nginx-nodejs.conf /etc/nginx/sites-available/nodejs-uygulama
sudo ln -s /etc/nginx/sites-available/nodejs-uygulama /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Bu kurulumla Nginx dışarıdan gelen trafiği alır ve Node.js uygulamanıza iletir. SSL sertifikası için Let’s Encrypt kullanıyorsanız Certbot otomatik olarak bu konfigürasyonu güncelleyecektir.

Güvenlik için Temel Önlemler

Production ortamında mutlaka almanız gereken birkaç önlem var.

# Helmet ile HTTP başlıklarını güvenli hale getir
npm install helmet

# Rate limiting için
npm install express-rate-limit

app.js‘e güvenlik middleware’lerini ekleyin:

cat > guvenlik.js << 'EOF'
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');

// Helmet middleware - güvenli HTTP başlıkları
const helmetMiddleware = helmet();

// Rate limiting - dakikada en fazla 100 istek
const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 dakika
  max: 100,
  mesaj: { hata: 'Çok fazla istek gönderdiniz, lütfen bekleyin.' },
  standardHeaders: true,
  legacyHeaders: false,
});

// Yönetim rotaları için daha sıkı limit
const adminLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 dakika
  max: 20,
});

module.exports = { helmetMiddleware, limiter, adminLimiter };
EOF

Bunları app.js‘de kullanmak için:

# app.js içine güvenlik modülünü ekle
cat >> app.js << 'EOF'

// NOT: Bu satırları app.use tanımlamalarının en üstüne taşıyın
const { helmetMiddleware, limiter } = require('./guvenlik');
// app.use(helmetMiddleware);
// app.use(limiter);
EOF

Ayrıca NODE_ENV=production olduğunda Express otomatik olarak bazı bilgileri gizler, hata mesajlarını sadeleştirir. Bu yüzden ortam değişkenini her zaman doğru ayarlayın.

Sık Karşılaşılan Sorunlar

Port zaten kullanımda hatası: EADDRINUSE hatası aldığınızda portu hangi uygulamanın kullandığını bulun:

# Portu kullanan süreci bul
lsof -i :3000

# Ya da
ss -tlnp | grep :3000

# Süreci kapat
kill -9 <PID>

node_modules sorunları: Bağımlılıklarda garip hatalar alıyorsanız temiz kurulum yapın:

rm -rf node_modules package-lock.json
npm cache clean --force
npm install

Bellek sızıntısı tespiti: Uygulamanız zamanla yavaşlıyorsa PM2’nin bellek limitini kullanın ya da --inspect flag’i ile Chrome DevTools ile profilleme yapın:

node --inspect app.js

Tarayıcıda chrome://inspect adresine gidip bağlanabilirsiniz.

Sonuç

Node.js ile HTTP sunucusu kurmak birkaç satır kod meselesi ama onu production’a taşımak başlı başına bir süreç. Özetlemek gerekirse: nvm ile Node.js sürüm yönetimini çözün, küçük projeler için core http modülü yeterli ama büyüyen projelerde Express kaçınılmaz, konfigürasyonu mutlaka ortam değişkenleri ile yönetin, production’da PM2 kullanın ve önüne Nginx koyun, güvenlik için en azından helmet ve rate limiting ekleyin.

Anlatılanları adım adım uygularsanız hem yerel geliştirme ortamında hem de production’da rahat çalışan, sorun çıktığında ne yapacağını bilen bir altyapıya sahip olursunuz. Bir sonraki adım olarak MongoDB veya PostgreSQL ile database entegrasyonuna bakabilirsiniz.

Bir yanıt yazın

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