Backend · Runtime · TypeScript

Node.js

Asenkron ve event-driven mimarisi sayesinde yüksek eşzamanlılık gerektiren projelerde Node.js'i tercih ediyoruz. Microservice, real-time ve API gateway çözümlerinde güçlü deneyimimizle hizmet veriyoruz.

Sürüm Node.js 22 LTS
Dil TypeScript 5.x
Paket Yöneticisi npm / pnpm
Ekosistem npmjs.com

Event-Driven, Non-Blocking Mimari

V8 motoru üzerinde çalışan Node.js, I/O yoğun iş yüklerinde olağanüstü performans sergiler. Tek iş parçacıklı event loop modeliyle binlerce eşzamanlı bağlantıyı sorunsuz yönetir. TypeScript ile birleştiğinde tip güvenliği, büyük ölçekli projelerde hız ve güvenilirliği bir arada sunar.

  • Non-blocking I/O ile düşük bellek ayak izi ve yüksek throughput
  • TypeScript ile end-to-end type safety — runtime hatalarını sıfıra yakın tutuyoruz
  • Worker Threads ile CPU yoğun işler izole edilmiş thread'lerde çalışır
  • npm ekosistemi — 2 milyondan fazla paket, her ihtiyaç için hazır çözüm
  • Monorepo desteği (Turborepo / Nx) ile büyük ekiplerde ölçeklenebilir yapı
  • Docker + PM2 cluster mode ile sıfır downtime deployment

Gerçek Proje Mimarisi

src/app.ts
TypeScript
import express, { Application, Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
import compression from 'compression';
import { pinoHttp } from 'pino-http';
import { logger } from './utils/logger';
import { errorHandler } from './middlewares/errorHandler';
import { authMiddleware } from './middlewares/auth';
import authRouter from './routes/auth.routes';
import ordersRouter from './routes/orders.routes';
import { env } from './config/env';

// ─── Request Logger ────────────────────────────────────────────
const requestLogger = pinoHttp({
  logger,
  customLogLevel: (_req, res) => (res.statusCode >= 500 ? 'error' : 'info'),
  serializers: {
    req: (req) => ({ method: req.method, url: req.url, id: req.id }),
    res: (res) => ({ statusCode: res.statusCode }),
  },
});

// ─── Rate Limiter ──────────────────────────────────────────────
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 dakika
  max: 200,
  standardHeaders: true,
  legacyHeaders: false,
  message: { success: false, error: 'Çok fazla istek, lütfen bekleyin.' },
  skip: (req) => req.ip === '127.0.0.1',
});

// ─── App Factory ───────────────────────────────────────────────
export function createApp(): Application {
  const app = express();

  // Güvenlik & sıkıştırma
  app.use(helmet({ contentSecurityPolicy: env.NODE_ENV === 'production' }));
  app.use(compression());
  app.use(cors({ origin: env.ALLOWED_ORIGINS, credentials: true }));

  // Parsing
  app.use(express.json({ limit: '10mb' }));
  app.use(express.urlencoded({ extended: true, limit: '10mb' }));

  // Logging & rate limiting
  app.use(requestLogger);
  app.use('/api', apiLimiter);

  // Sağlık kontrolü (auth gerektirmez)
  app.get('/health', (_req: Request, res: Response) => {
    res.json({ status: 'ok', uptime: process.uptime(), timestamp: Date.now() });
  });

  // Rotalar
  app.use('/api/v1/auth', authRouter);
  app.use('/api/v1/orders', authMiddleware, ordersRouter);

  // 404 handler
  app.use((_req: Request, res: Response) => {
    res.status(404).json({ success: false, error: 'Endpoint bulunamadı.' });
  });

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

  return app;
}
src/sockets/chatHandler.ts
TypeScript
import { Server, Socket } from 'socket.io';
import { MessageService } from '../services/message.service';
import { RoomService } from '../services/room.service';
import { logger } from '../utils/logger';

interface JoinPayload  { roomId: string; }
interface SendPayload  { roomId: string; content: string; replyTo?: string; }
interface TypingPayload { roomId: string; }

export function registerChatHandlers(
  io: Server,
  socket: Socket,
  messageService: MessageService,
  roomService: RoomService,
): void {
  const userId = socket.data.userId as string;

  // ─── Odaya Katıl ──────────────────────────────────────────
  socket.on('room:join', async ({ roomId }: JoinPayload) => {
    const isMember = await roomService.isMember(roomId, userId);
    if (!isMember) {
      socket.emit('error', { code: 'FORBIDDEN', message: 'Bu odaya erişim izniniz yok.' });
      return;
    }

    await socket.join(roomId);
    await roomService.setOnline(roomId, userId);

    // Odadaki diğer kullanıcılara bildir
    socket.to(roomId).emit('user:joined', { userId, roomId, at: new Date().toISOString() });
    logger.info({ userId, roomId }, 'Socket odaya katıldı');
  });

  // ─── Mesaj Gönder ─────────────────────────────────────────
  socket.on('message:send', async (payload: SendPayload, ack) => {
    try {
      const message = await messageService.create({
        roomId:  payload.roomId,
        senderId: userId,
        content: payload.content,
        replyTo: payload.replyTo,
      });

      // Odanın tüm üyelerine yayınla (gönderen dahil)
      io.to(payload.roomId).emit('message:new', {
        id:        message.id,
        content:   message.content,
        senderId:  message.senderId,
        roomId:    message.roomId,
        replyTo:   message.replyTo,
        createdAt: message.createdAt,
      });

      // ACK ile client'a onay
      ack?.({ success: true, messageId: message.id });
    } catch (err) {
      logger.error({ err, payload }, 'Mesaj gönderilemedi');
      ack?.({ success: false, error: 'Mesaj gönderilemedi.' });
    }
  });

  // ─── Yazıyor Göstergesi ───────────────────────────────────
  socket.on('typing:start', ({ roomId }: TypingPayload) => {
    socket.to(roomId).emit('typing:update', { userId, roomId, isTyping: true });

    // 3 sn sonra otomatik kapat
    setTimeout(() => {
      socket.to(roomId).emit('typing:update', { userId, roomId, isTyping: false });
    }, 3000);
  });

  // ─── Bağlantı Kesme Temizliği ─────────────────────────────
  socket.on('disconnect', async (reason) => {
    logger.info({ userId, reason }, 'Socket bağlantısı kesildi');

    // Kullanıcının bulunduğu tüm odalarda offline yap
    const rooms = Array.from(socket.rooms).filter((r) => r !== socket.id);
    await Promise.allSettled(
      rooms.map((roomId) =>
        roomService.setOffline(roomId, userId).then(() =>
          io.to(roomId).emit('user:left', { userId, roomId }),
        ),
      ),
    );
  });
}
src/queues/emailQueue.ts
TypeScript
import { Queue, Worker, Job, QueueEvents } from 'bullmq';
import { redisConnection } from '../config/redis';
import { EmailService } from '../services/email.service';
import { logger } from '../utils/logger';

// ─── Job Tipleri ──────────────────────────────────────────────
export interface WelcomeEmailJob {
  type: 'welcome';
  userId: string;
  email: string;
  firstName: string;
}

export interface OrderConfirmEmailJob {
  type: 'order_confirm';
  orderId: string;
  email: string;
  items: { name: string; qty: number; price: number }[];
}

export type EmailJob = WelcomeEmailJob | OrderConfirmEmailJob;

// ─── Queue Tanımı ─────────────────────────────────────────────
export const emailQueue = new Queue('email', {
  connection: redisConnection,
  defaultJobOptions: {
    attempts: 3,
    backoff: { type: 'exponential', delay: 5000 },
    removeOnComplete: { count: 500, age: 24 * 3600 },
    removeOnFail:    { count: 200, age: 72 * 3600 },
  },
});

// ─── Worker ───────────────────────────────────────────────────
const emailWorker = new Worker(
  'email',
  async (job: Job) => {
    const emailService = new EmailService();

    await job.updateProgress(10);

    switch (job.data.type) {
      case 'welcome': {
        const { email, firstName } = job.data;
        logger.info({ jobId: job.id, email }, 'Hoşgeldin maili gönderiliyor');

        await emailService.sendWelcome({ to: email, firstName });
        await job.updateProgress(100);
        break;
      }

      case 'order_confirm': {
        const { orderId, email, items } = job.data;
        logger.info({ jobId: job.id, orderId }, 'Sipariş onay maili gönderiliyor');

        await job.updateProgress(30);
        const total = items.reduce((s, i) => s + i.qty * i.price, 0);
        await emailService.sendOrderConfirmation({ to: email, orderId, items, total });
        await job.updateProgress(100);
        break;
      }

      default:
        throw new Error(`Bilinmeyen email job tipi`);
    }
  },
  {
    connection: redisConnection,
    concurrency: 5,
    limiter: { max: 50, duration: 60_000 }, // dakikada maks 50 mail
  },
);

// ─── Worker Event Dinleyicileri ───────────────────────────────
emailWorker.on('completed', (job) => {
  logger.info({ jobId: job.id, type: job.data.type }, 'Email job tamamlandı');
});

emailWorker.on('failed', (job, err) => {
  logger.error(
    { jobId: job?.id, attempt: job?.attemptsMade, err: err.message },
    'Email job başarısız',
  );
});

// ─── Queue Events (dışarıdan izleme) ─────────────────────────
export const emailQueueEvents = new QueueEvents('email', { connection: redisConnection });

// ─── Helper: Job Ekle ─────────────────────────────────────────
export async function enqueueEmail(data: EmailJob, delay = 0) {
  return emailQueue.add(data.type, data, { delay });
}
src/repositories/order.repository.ts
TypeScript
import { PrismaClient, Order, OrderStatus, Prisma } from '@prisma/client';
import { Injectable } from '../di/injectable';
import { PaginatedResult, PaginationParams } from '../types/pagination';

export interface OrderFilters {
  userId?:   string;
  status?:   OrderStatus;
  fromDate?: Date;
  toDate?:   Date;
  minTotal?: number;
  maxTotal?: number;
}

// Prisma include tipi — UI'ye döndürülecek ilişkiler
const orderWithRelations = Prisma.validator()({
  include: {
    items: { include: { product: { select: { id: true, name: true, sku: true } } } },
    user:  { select: { id: true, firstName: true, lastName: true, email: true } },
    shippingAddress: true,
  },
});
type OrderWithRelations = Prisma.OrderGetPayload;

@Injectable()
export class OrderRepository {
  constructor(private readonly prisma: PrismaClient) {}

  async findMany(
    filters: OrderFilters,
    pagination: PaginationParams,
  ): Promise> {
    const where: Prisma.OrderWhereInput = {
      ...(filters.userId && { userId: filters.userId }),
      ...(filters.status && { status: filters.status }),
      ...(filters.minTotal !== undefined && {
        total: { gte: filters.minTotal },
      }),
      ...(filters.maxTotal !== undefined && {
        total: { ...(filters.minTotal !== undefined ? { gte: filters.minTotal } : {}), lte: filters.maxTotal },
      }),
      ...((filters.fromDate || filters.toDate) && {
        createdAt: {
          ...(filters.fromDate && { gte: filters.fromDate }),
          ...(filters.toDate  && { lte: filters.toDate  }),
        },
      }),
      deletedAt: null, // soft-delete filtresi
    };

    const { page, limit } = pagination;
    const skip = (page - 1) * limit;

    // Tek transaction içinde count + data — tutarlılık garantisi
    const [total, orders] = await this.prisma.$transaction([
      this.prisma.order.count({ where }),
      this.prisma.order.findMany({
        where,
        ...orderWithRelations,
        orderBy: { createdAt: 'desc' },
        skip,
        take: limit,
      }),
    ]);

    return {
      data: orders,
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
        hasNextPage: page * limit < total,
      },
    };
  }

  async findByIdOrThrow(id: string, userId?: string): Promise {
    const order = await this.prisma.order.findFirst({
      where: { id, ...(userId && { userId }), deletedAt: null },
      ...orderWithRelations,
    });

    if (!order) {
      throw new Error(`Sipariş bulunamadı: ${id}`);
    }
    return order;
  }

  async updateStatus(id: string, status: OrderStatus): Promise {
    return this.prisma.order.update({
      where: { id },
      data:  { status, updatedAt: new Date() },
    });
  }
}

Kullandığımız Paketler

express / fastify
HTTP Framework

REST API ve middleware zinciri için olgun, battle-tested HTTP sunucu çerçeveleri. Fastify düşük overhead ile yüksek performans sunar.

@prisma/client
ORM

Tip güvenli, otomatik tamamlama destekli modern TypeScript ORM. Migration, seeding ve Prisma Studio ile tam veri yönetimi.

bullmq
Job Queue

Redis tabanlı, güvenilir iş kuyruğu. Rate limiter, priority queue, repeatable jobs ve delayed job desteğiyle enterprise düzey arka plan işleme.

socket.io
WebSocket

Çift yönlü gerçek zamanlı iletişim. Oda yönetimi, namespace, Redis adaptörüyle yatay ölçekleme ve otomatik reconnect desteği.

passport-jwt
Auth

JWT tabanlı kimlik doğrulama stratejisi. Access + refresh token rotasyonu, cookie-based güvenlik ve çoklu strateji desteği.

zod
Validation

TypeScript-first runtime doğrulama kütüphanesi. Schema'dan otomatik tip çıkarımı, request body, query param ve response validasyonu.

winston / pino
Logging

Yapılandırılmış JSON log üretimi. Log seviyeleri, dosyaya yazma, Elasticsearch/Datadog entegrasyonu ve async logging desteği.

pm2
Process Manager

Cluster mode ile çoklu CPU çekirdeği kullanımı, sıfır downtime reload, cron-based restart ve dahili izleme paneli.

Hangi Projelerde Node.js?

💬

Real-time Chat & Bildirim

Socket.io ve Redis Pub/Sub ile binlerce eşzamanlı kullanıcının mesajlaştığı, anlık bildirim aldığı sistemler.

🧩

Microservice Mimarisi

gRPC, NATS veya RabbitMQ üzerinden haberleşen, Docker/Kubernetes ortamında yatay olarak ölçeklenen servis katmanları.

🔀

API Gateway

Kimlik doğrulama, rate limiting, request routing ve circuit breaker içeren merkezi giriş noktası servisleri.

📡

WebSocket Sunucusu

Canlı fiyat akışı, borsa verileri, IoT cihaz telemetrisi ve oyun sunucuları için düşük gecikmeli kalıcı bağlantılar.

Yüksek Trafikli REST API

Saniyede binlerce isteği karşılayan, Redis cache katmanlı, CDN entegrasyonlu ve otomatik ölçeklenen API servisleri.

🛠️

CLI Araçları & Otomasyon

Veri migrasyon scriptleri, CI/CD araçları, cron tabanlı raporlama ve toplu işlem botları.

Node.js ile hızlı ve ölçeklenebilir bir backend mi istiyorsunuz?

Ekibimiz, TypeScript ve Node.js ekosistemiyle enterprise düzeyde backend çözümleri geliştiriyor. Projenizi birlikte konuşalım.

Ücretsiz Danışma Al