Node.js, Chrome'un V8 JavaScript motorunu kullanarak JavaScript'i sunucu tarafında çalıştıran bir ortamdır. Express.js ile birlikte hızlı ve ölçeklenebilir REST API'ler inşa etmenin en popüler yollarından biridir.
Kurulum ve İlk Proje
Temel Express Sunucusu
// TYPESCRIPT //
// src/server.ts
import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import 'dotenv/config';
const app = express();
// Middleware'ler
app.use(helmet()); // Güvenlik başlıkları
app.use(cors({ origin: process.env.CORS_ORIGIN }));
app.use(express.json({ limit: '10mb' })); // JSON body parser
app.use(morgan('combined')); // HTTP loglama
// Health check
app.get('/health', (_req, res) => {
res.json({ durum: 'sağlıklı', zaman: new Date().toISOString() });
});
// 404 handler
app.use((_req, res) => {
res.status(404).json({ hata: 'Endpoint bulunamadı.' });
});
// Global hata handler
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
console.error(err.stack);
res.status(500).json({ hata: 'Sunucu hatası oluştu.' });
});
const PORT = Number(process.env.PORT) || 3000;
app.listen(PORT, () => {
console.log(`Sunucu http://localhost:${PORT} adresinde çalışıyor`);
});
export default app;Router ve Controller Mimarisi
// TYPESCRIPT //
// src/routes/makale.routes.ts
import { Router } from 'express';
import { MakaleController } from '../controllers/makale.controller';
import { kimlikDogrula } from '../middleware/auth';
const router = Router();
const controller = new MakaleController();
router.get('/', controller.listele);
router.get('/:id', controller.getir);
router.post('/', kimlikDogrula, controller.olustur);
router.put('/:id', kimlikDogrula, controller.guncelle);
router.delete('/:id', kimlikDogrula, controller.sil);
export default router;
// src/controllers/makale.controller.ts
import { Request, Response, NextFunction } from 'express';
import { MakaleService } from '../services/makale.service';
import { makaleOlusturSema } from '../validators/makale.validator';
export class MakaleController {
private service = new MakaleService();
listele = async (req: Request, res: Response, next: NextFunction) => {
try {
const sayfa = Number(req.query.sayfa) || 1;
const boyut = Number(req.query.boyut) || 20;
const aramaKelimesi = req.query.arama as string | undefined;
const sonuc = await this.service.listele({ sayfa, boyut, aramaKelimesi });
res.json(sonuc);
} catch (err) {
next(err);
}
};
getir = async (req: Request, res: Response, next: NextFunction) => {
try {
const makale = await this.service.getir(req.params.id);
if (!makale) return res.status(404).json({ hata: 'Makale bulunamadı.' });
res.json(makale);
} catch (err) {
next(err);
}
};
olustur = async (req: Request, res: Response, next: NextFunction) => {
try {
const dogrulanmis = makaleOlusturSema.parse(req.body);
const makale = await this.service.olustur(dogrulanmis, req.user!.id);
res.status(201).json(makale);
} catch (err) {
next(err);
}
};
guncelle = async (req: Request, res: Response, next: NextFunction) => {
try {
const makale = await this.service.guncelle(req.params.id, req.body, req.user!.id);
res.json(makale);
} catch (err) {
next(err);
}
};
sil = async (req: Request, res: Response, next: NextFunction) => {
try {
await this.service.sil(req.params.id, req.user!.id);
res.status(204).send();
} catch (err) {
next(err);
}
};
}Middleware Yazımı
// TYPESCRIPT //
// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
interface JWTPayload {
userId: string;
rol: string;
iat: number;
exp: number;
}
// Express Request'e user alanı ekle
declare global {
namespace Express {
interface Request {
user?: { id: string; rol: string };
}
}
}
export function kimlikDogrula(req: Request, res: Response, next: NextFunction) {
const authBaslik = req.headers.authorization;
if (!authBaslik?.startsWith('Bearer ')) {
return res.status(401).json({ hata: 'Yetkilendirme başlığı eksik.' });
}
const token = authBaslik.slice(7);
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
req.user = { id: payload.userId, rol: payload.rol };
next();
} catch {
res.status(401).json({ hata: 'Geçersiz veya süresi dolmuş token.' });
}
}
export function rolGerektir(...roller: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user || !roller.includes(req.user.rol)) {
return res.status(403).json({ hata: 'Bu işlem için yetkiniz yok.' });
}
next();
};
}Zod ile Input Validation
// TYPESCRIPT //
// src/validators/makale.validator.ts
import { z } from 'zod';
export const makaleOlusturSema = z.object({
baslik: z.string().min(5).max(300),
icerik: z.string().min(100),
slug: z.string().regex(/^[a-z0-9-]+$/, 'Sadece küçük harf, rakam ve tire').optional(),
etiketler: z.array(z.string()).max(10).default([]),
yayinla: z.boolean().default(false),
});
export type MakaleOlusturDto = z.infer<typeof makaleOlusturSema>;
// Global Zod hata handler
// src/middleware/validasyon-hatasi.ts
import { ZodError } from 'zod';
export function validasyonHatasiHandler(
err: unknown,
req: Request,
res: Response,
next: NextFunction
) {
if (err instanceof ZodError) {
return res.status(422).json({
hata: 'Validasyon hatası',
detaylar: err.flatten().fieldErrors,
});
}
next(err);
}Veritabanı Bağlantısı (Prisma)
// TYPESCRIPT //
// src/lib/db.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
log: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
// src/services/makale.service.ts
import { prisma } from '../lib/db';
import type { MakaleOlusturDto } from '../validators/makale.validator';
export class MakaleService {
async listele({ sayfa, boyut, aramaKelimesi }: {
sayfa: number; boyut: number; aramaKelimesi?: string;
}) {
const atla = (sayfa - 1) * boyut;
const where = aramaKelimesi ? {
OR: [
{ baslik: { contains: aramaKelimesi, mode: 'insensitive' as const } },
{ icerik: { contains: aramaKelimesi, mode: 'insensitive' as const } },
],
} : {};
const [makaleler, toplam] = await Promise.all([
prisma.makale.findMany({
where,
skip: atla,
take: boyut,
orderBy: { olusturuldu: 'desc' },
select: { id: true, baslik: true, slug: true, yazar: { select: { ad: true } } },
}),
prisma.makale.count({ where }),
]);
return {
veri: makaleler,
toplam,
sayfa,
sonSayfa: Math.ceil(toplam / boyut),
};
}
async olustur(dto: MakaleOlusturDto, yazarId: string) {
const slug = dto.slug ?? dto.baslik
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-');
return prisma.makale.create({
data: {
...dto,
slug,
yazarId,
},
});
}
}Rate Limiting
// TYPESCRIPT //
// src/middleware/rate-limit.ts
import rateLimit from 'express-rate-limit';
export const genelLimit = rateLimit({
windowMs: 15 * 60 * 1000, // 15 dakika
max: 100, // IP başına 100 istek
standardHeaders: true,
legacyHeaders: false,
message: { hata: 'Çok fazla istek gönderildi. Lütfen bekleyin.' },
});
export const girislimit = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Giriş denemesi limiti
skipSuccessfulRequests: true,
message: { hata: 'Çok fazla giriş denemesi. 15 dakika bekleyin.' },
});
// server.ts
app.use('/api/', genelLimit);
app.use('/api/auth/giris', girislimit);Sonuç
Express.js ile Router, Controller, Service katmanı ve Middleware zinciri kurduğunuzda, bakımı kolay ve ölçeklenebilir bir API mimarisi elde edersiniz. Zod validation, Prisma ORM ve rate limiting ile üretim kalitesine ulaşırsınız. Bir sonraki derste JWT auth akışını ve refresh token yönetimini derinlemesine inceleyeceğiz.