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