REST API'ler, modern web ve mobil uygulamaların omurgasını oluşturur. Bu rehberde Node.js ve Express.js kullanarak endüstri standartlarına uygun, güvenli ve ölçeklenebilir bir REST API geliştireceğiz. Proje yapısından JWT kimlik doğrulamaya, hata yönetiminden deployment'a kadar tüm aşamaları gerçek kod örnekleriyle ele alacağız.

Proje Kurulumu ve Yapısı

Ölçeklenebilir bir API projesi için düzgün bir dizin yapısı kritik öneme sahiptir. Katmanlı mimari (layered architecture) kullanarak endişelerin ayrımını (separation of concerns) sağlayacağız.

# Proje oluştur
mkdir my-api && cd my-api
npm init -y

# Temel bağımlılıklar
npm install express dotenv cors helmet morgan
npm install jsonwebtoken bcryptjs
npm install pg          # PostgreSQL için
npm install joi         # Validasyon için

# Geliştirme bağımlılıkları
npm install -D nodemon eslint
my-api/
├── src/
│   ├── config/
│   │   └── db.js          # Veritabanı bağlantısı
│   ├── middleware/
│   │   ├── auth.js         # JWT doğrulama
│   │   ├── validate.js     # İstek validasyonu
│   │   └── errorHandler.js # Merkezi hata yönetimi
│   ├── routes/
│   │   ├── auth.js         # Giriş/kayıt rotaları
│   │   └── users.js        # Kullanıcı CRUD rotaları
│   ├── controllers/
│   │   ├── authController.js
│   │   └── userController.js
│   ├── models/
│   │   └── userModel.js
│   └── app.js              # Express uygulaması
├── .env
├── .gitignore
└── package.json

Express.js Uygulaması Oluşturma

Express uygulamasını güvenlik middleware'leri ile birlikte yapılandıralım. Helmet HTTP başlıklarını güvenli hale getirir, CORS cross-origin istekleri kontrol eder, Morgan ise HTTP isteklerini loglar.

// src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
require('dotenv').config();

const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');
const errorHandler = require('./middleware/errorHandler');

const app = express();

// Güvenlik ve yardımcı middleware'ler
app.use(helmet());
app.use(cors({ origin: process.env.CORS_ORIGIN || '*' }));
app.use(morgan('combined'));
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true }));

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// API rotaları
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);

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

// Merkezi hata yönetimi
app.use(errorHandler);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`API sunucusu ${PORT} portunda çalışıyor`);
});

module.exports = app;

Routing ve Controller Yapısı

Express Router ile rotaları modüler hale getiriyoruz. Her route dosyası ilgili controller fonksiyonlarına yönlendirir. Bu sayede iş mantığı (business logic) rota tanımlarından ayrılmış olur.

// src/routes/users.js
const router = require('express').Router();
const { getUsers, getUserById, updateUser, deleteUser } = require('../controllers/userController');
const auth = require('../middleware/auth');
const { validateUser } = require('../middleware/validate');

router.get('/',          auth, getUsers);
router.get('/:id',       auth, getUserById);
router.put('/:id',       auth, validateUser, updateUser);
router.delete('/:id',    auth, deleteUser);

module.exports = router;
// src/controllers/userController.js
const pool = require('../config/db');

exports.getUsers = async (req, res, next) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 20;
    const offset = (page - 1) * limit;

    const { rows } = await pool.query(
      'SELECT id, name, email, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2',
      [limit, offset]
    );

    const { rows: countResult } = await pool.query('SELECT count(*) FROM users');
    const total = parseInt(countResult[0].count);

    res.json({
      data: rows,
      pagination: {
        page,
        limit,
        total,
        totalPages: Math.ceil(total / limit)
      }
    });
  } catch (err) {
    next(err);
  }
};

exports.getUserById = async (req, res, next) => {
  try {
    const { rows } = await pool.query(
      'SELECT id, name, email, created_at FROM users WHERE id = $1',
      [req.params.id]
    );
    if (!rows.length) {
      return res.status(404).json({ error: 'Kullanıcı bulunamadı' });
    }
    res.json({ data: rows[0] });
  } catch (err) {
    next(err);
  }
};

Middleware Katmanı

Middleware'ler, istek-yanıt döngüsünde araya giren fonksiyonlardır. Kimlik doğrulama, validasyon, loglama ve hata yönetimi gibi çapraz kesim endişelerini (cross-cutting concerns) middleware olarak yazarız.

Validasyon Middleware

// src/middleware/validate.js
const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().min(2).max(100).required()
    .messages({ 'string.min': 'İsim en az 2 karakter olmalı' }),
  email: Joi.string().email().required()
    .messages({ 'string.email': 'Geçerli bir e-posta adresi girin' }),
  password: Joi.string().min(8).pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).required()
    .messages({ 'string.pattern.base': 'Şifre en az 1 büyük harf, 1 küçük harf ve 1 rakam içermeli' })
});

exports.validateUser = (req, res, next) => {
  const { error } = userSchema.validate(req.body, { abortEarly: false });
  if (error) {
    const messages = error.details.map(d => d.message);
    return res.status(400).json({ errors: messages });
  }
  next();
};

Hata Yönetimi Middleware

// src/middleware/errorHandler.js
module.exports = (err, req, res, next) => {
  console.error(`[${new Date().toISOString()}] ${err.stack || err.message}`);

  // Bilinen hata tipleri
  if (err.name === 'ValidationError') {
    return res.status(400).json({ error: err.message });
  }
  if (err.name === 'UnauthorizedError' || err.status === 401) {
    return res.status(401).json({ error: 'Yetkisiz erişim' });
  }
  if (err.code === '23505') { // PostgreSQL unique violation
    return res.status(409).json({ error: 'Bu kayıt zaten mevcut' });
  }

  // Genel hata
  const statusCode = err.status || 500;
  res.status(statusCode).json({
    error: process.env.NODE_ENV === 'production'
      ? 'Sunucu hatası'
      : err.message
  });
};

JWT Kimlik Doğrulama

JSON Web Token (JWT), stateless kimlik doğrulama mekanizmasıdır. Kullanıcı giriş yaptığında bir token üretilir ve sonraki isteklerde Authorization header'ında gönderilir. Sunucu, her istekte token'ı doğrulayarak kullanıcıyı tanır.

// src/middleware/auth.js
const jwt = require('jsonwebtoken');

module.exports = (req, res, next) => {
  const header = req.headers.authorization;
  if (!header || !header.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Token gerekli' });
  }

  const token = header.split(' ')[1];
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // { id, email, role }
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token süresi dolmuş' });
    }
    return res.status(401).json({ error: 'Geçersiz token' });
  }
};
// src/controllers/authController.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const pool = require('../config/db');

exports.register = async (req, res, next) => {
  try {
    const { name, email, password } = req.body;

    // E-posta kontrolü
    const existing = await pool.query('SELECT id FROM users WHERE email = $1', [email]);
    if (existing.rows.length) {
      return res.status(409).json({ error: 'Bu e-posta zaten kayıtlı' });
    }

    // Şifreyi hashle
    const salt = await bcrypt.genSalt(12);
    const hashedPassword = await bcrypt.hash(password, salt);

    // Kullanıcıyı oluştur
    const { rows } = await pool.query(
      'INSERT INTO users (name, email, password) VALUES ($1, $2, $3) RETURNING id, name, email',
      [name, email, hashedPassword]
    );

    // Token üret
    const token = jwt.sign(
      { id: rows[0].id, email: rows[0].email, role: 'user' },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.status(201).json({ user: rows[0], token });
  } catch (err) {
    next(err);
  }
};

exports.login = async (req, res, next) => {
  try {
    const { email, password } = req.body;

    const { rows } = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
    if (!rows.length) {
      return res.status(401).json({ error: 'E-posta veya şifre hatalı' });
    }

    const user = rows[0];
    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) {
      return res.status(401).json({ error: 'E-posta veya şifre hatalı' });
    }

    const token = jwt.sign(
      { id: user.id, email: user.email, role: user.role || 'user' },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.json({ user: { id: user.id, name: user.name, email: user.email }, token });
  } catch (err) {
    next(err);
  }
};

PostgreSQL Veritabanı Bağlantısı

// src/config/db.js
const { Pool } = require('pg');

const pool = new Pool({
  host: process.env.DB_HOST || 'localhost',
  port: process.env.DB_PORT || 5432,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 5000
});

pool.on('error', (err) => {
  console.error('Veritabanı bağlantı hatası:', err.message);
});

// Bağlantıyı test et
pool.query('SELECT NOW()')
  .then(() => console.log('PostgreSQL bağlantısı başarılı'))
  .catch(err => console.error('PostgreSQL bağlantı hatası:', err.message));

module.exports = pool;
# .env dosyası
PORT=3000
NODE_ENV=development
CORS_ORIGIN=http://localhost:5173
JWT_SECRET=en-az-32-karakter-uzunlugunda-gizli-anahtar
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapi_db
DB_USER=myapi_user
DB_PASS=guclu_sifre

Rate Limiting ve Güvenlik

API'nizi kötüye kullanıma karşı korumak için rate limiting uygulamanız gerekir. express-rate-limit paketi ile belirli bir zaman diliminde yapılabilecek istek sayısını sınırlayabilirsiniz.

npm install express-rate-limit
// Rate limiter yapılandırması
const rateLimit = require('express-rate-limit');

// Genel limiter — tüm endpointler için
const generalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 dakika
  max: 100,                  // pencere başına max istek
  message: { error: 'Çok fazla istek — 15 dakika sonra tekrar deneyin' },
  standardHeaders: true,
  legacyHeaders: false
});

// Auth limiter — giriş/kayıt için daha katı
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10,
  message: { error: 'Çok fazla giriş denemesi' }
});

app.use('/api', generalLimiter);
app.use('/api/auth', authLimiter);

Deployment (Sunucuya Yayınlama)

API'nizi VPS üzerinde PM2 process manager ile çalıştırabilirsiniz. PM2, uygulamanızı arka planda çalıştırır, çökmelerde otomatik yeniden başlatır ve cluster modunda birden fazla CPU çekirdeğini kullanmanızı sağlar.

# PM2 kurulumu
npm install -g pm2

# Uygulamayı başlat
pm2 start src/app.js --name "my-api" -i max

# Yararlı PM2 komutları
pm2 list              # Çalışan uygulamalar
pm2 logs my-api       # Canlı loglar
pm2 monit             # CPU/RAM izleme
pm2 restart my-api    # Yeniden başlat
pm2 save              # Mevcut listeyi kaydet
pm2 startup           # Sistem açılışında otomatik başlat
// ecosystem.config.js — PM2 yapılandırma dosyası
module.exports = {
  apps: [{
    name: 'my-api',
    script: 'src/app.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'development',
      PORT: 3000
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    max_memory_restart: '500M',
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    error_file: 'logs/error.log',
    out_file: 'logs/app.log'
  }]
};

API Testi

curl ile API endpointlerinizi hızlıca test edebilirsiniz:

# Kayıt
curl -X POST http://localhost:3000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"name":"Ali Yılmaz","email":"ali@test.com","password":"Test1234"}'

# Giriş
curl -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"ali@test.com","password":"Test1234"}'

# Korumalı endpoint (token ile)
curl http://localhost:3000/api/users \
  -H "Authorization: Bearer BURAYA_TOKEN_YAPISTIR"

# Health check
curl http://localhost:3000/health

Node.js uygulamalarınız için optimize edilmiş VPS hosting çözümlerimizi inceleyin.