İçeriğe geç
Sedat Demir
Geri dön

ElysiaJS ile Bun Üzerinde Ultra-Hızlı TypeScript API Geliştirme

ElysiaJS ile Bun Üzerinde Ultra-Hızlı TypeScript API Geliştirme

Backend geliştirme dünyası hızla evrilirken, performans ve geliştirici deneyimi arasındaki denge her zamankinden daha önemli hale geldi. Bun runtime'ının sunduğu ham hız ile ElysiaJS framework'ünün ergonomik API tasarımı bir araya geldiğinde, ortaya TypeScript ekosisteminin en hızlı web framework'lerinden biri çıkıyor.

Bu yazıda ElysiaJS'in ne olduğunu, neden bu kadar hızlı olduğunu ve sıfırdan production-ready bir API nasıl geliştireceğinizi detaylıca inceleyeceğiz.


Bun Nedir ve Neden Önemli?

Bun, Jarred Sumner tarafından geliştirilen ve JavaScriptCore motorunu kullanan bir JavaScript/TypeScript runtime'ıdır. Node.js'in kullandığı V8 motorunun aksine, Apple'ın Safari tarayıcısında kullandığı JavaScriptCore motoru üzerine inşa edilmiştir.

Bun'un öne çıkan özellikleri:

# Bun kurulumu (macOS, Linux, WSL)
curl -fsSL https://bun.sh/install | bash

# Versiyon kontrolü
bun --version

ElysiaJS'e Giriş

ElysiaJS, Bun runtime'ı için özel olarak optimize edilmiş, end-to-end type safety sunan bir TypeScript web framework'üdür. Benchmark'larda Express.js'ten 18 kata kadar, Fastify'dan ise 3-4 kata kadar daha hızlı performans göstermektedir.

ElysiaJS'in Temel Avantajları

  1. Statik Kod Analizi: ElysiaJS, route handler'larınızı derleme zamanında analiz ederek gereksiz overhead'i ortadan kaldırır
  2. End-to-End Type Safety: Request'ten response'a kadar tam tip güvenliği
  3. Eden Treaty: Frontend ile backend arasında otomatik tip senkronizasyonu
  4. Plugin Ekosistemi: Swagger, JWT, CORS gibi hazır plugin'ler
  5. Ergonomik API: Temiz, zincirleme (chainable) method yapısı

Proje Kurulumu

Haydi sıfırdan bir ElysiaJS projesi oluşturalım:

# Yeni proje oluşturma
bun create elysia my-api
cd my-api

# Bağımlılıkları yükleme
bun install

# Geliştirme sunucusunu başlatma
bun run dev

Proje yapısı şu şekilde oluşacaktır:

my-api/
├── src/
│   └── index.ts
├── package.json
├── tsconfig.json
└── bun.lockb

İlk API Endpoint'imiz

src/index.ts dosyasını açalım ve ilk API'mizi yazalım:

import { Elysia } from 'elysia';

const app = new Elysia()
  .get('/', () => 'Merhaba ElysiaJS! 🦊')
  .get('/api/health', () => ({
    status: 'ok',
    timestamp: new Date().toISOString(),
    runtime: 'Bun',
    framework: 'ElysiaJS'
  }))
  .listen(3000);

console.log(
  `🦊 ElysiaJS sunucusu http://${app.server?.hostname}:${app.server?.port} adresinde çalışıyor`
);

Bu kadar! Sadece birkaç satır kodla çalışan bir API sunucunuz var. bun run src/index.ts komutuyla çalıştırabilirsiniz.


Route Yapısı ve HTTP Metodları

ElysiaJS, tüm standart HTTP metodlarını destekler ve zincirleme (method chaining) yapısıyla son derece okunabilir bir kod yazmanızı sağlar:

import { Elysia, t } from 'elysia';

interface User {
  id: number;
  name: string;
  email: string;
}

const users: User[] = [
  { id: 1, name: 'Ahmet Yılmaz', email: 'ahmet@example.com' },
  { id: 2, name: 'Elif Demir', email: 'elif@example.com' }
];

const app = new Elysia()
  // Tüm kullanıcıları listele
  .get('/api/users', () => users)

  // ID ile kullanıcı getir
  .get('/api/users/:id', ({ params: { id } }) => {
    const user = users.find(u => u.id === Number(id));
    if (!user) {
      throw new Error('Kullanıcı bulunamadı');
    }
    return user;
  })

  // Yeni kullanıcı oluştur
  .post('/api/users', ({ body }) => {
    const newUser: User = {
      id: users.length + 1,
      ...body as Omit<User, 'id'>
    };
    users.push(newUser);
    return { message: 'Kullanıcı oluşturuldu', user: newUser };
  })

  // Kullanıcı güncelle
  .put('/api/users/:id', ({ params: { id }, body }) => {
    const index = users.findIndex(u => u.id === Number(id));
    if (index === -1) throw new Error('Kullanıcı bulunamadı');
    users[index] = { ...users[index], ...body as Partial<User> };
    return { message: 'Kullanıcı güncellendi', user: users[index] };
  })

  // Kullanıcı sil
  .delete('/api/users/:id', ({ params: { id } }) => {
    const index = users.findIndex(u => u.id === Number(id));
    if (index === -1) throw new Error('Kullanıcı bulunamadı');
    const deleted = users.splice(index, 1);
    return { message: 'Kullanıcı silindi', user: deleted[0] };
  })

  .listen(3000);

Validasyon ile Type Safety

ElysiaJS'in en güçlü özelliklerinden biri TypeBox tabanlı şema validasyonudur. t modülü ile request ve response'larınızı hem runtime'da hem de derleme zamanında doğrulayabilirsiniz:

import { Elysia, t } from 'elysia';

const app = new Elysia()
  .post(
    '/api/users',
    ({ body }) => {
      // body otomatik olarak tiplenmiştir!
      // body.name -> string
      // body.email -> string
      // body.age -> number
      return {
        message: `Hoş geldin, ${body.name}!`,
        user: { id: Date.now(), ...body }
      };
    },
    {
      body: t.Object({
        name: t.String({ minLength: 2, maxLength: 50 }),
        email: t.String({ format: 'email' }),
        age: t.Number({ minimum: 18, maximum: 120 })
      }),
      response: t.Object({
        message: t.String(),
        user: t.Object({
          id: t.Number(),
          name: t.String(),
          email: t.String(),
          age: t.Number()
        })
      }),
      detail: {
        tags: ['Kullanıcılar'],
        summary: 'Yeni kullanıcı oluştur'
      }
    }
  )
  .listen(3000);

Yanlış bir request gönderildiğinde ElysiaJS otomatik olarak anlamlı hata mesajları döner:

{
  "type": "validation",
  "on": "body",
  "message": "Expected string with format 'email'",
  "expected": "{ name: string, email: string, age: number }",
  "found": "{ name: 'Test', email: 'invalid', age: 25 }"
}

Plugin Sistemi ve Modüler Yapı

ElysiaJS'in plugin sistemi, kodunuzu modüler parçalara ayırmanızı ve yeniden kullanılabilir bileşenler oluşturmanızı sağlar:

// plugins/auth.ts
import { Elysia, t } from 'elysia';
import { jwt } from '@elysiajs/jwt';

export const authPlugin = new Elysia({ name: 'auth' })
  .use(
    jwt({
      name: 'jwt',
      secret: process.env.JWT_SECRET || 'super-gizli-anahtar'
    })
  )
  .derive(async ({ jwt, headers }) => {
    const token = headers.authorization?.replace('Bearer ', '');

    if (!token) {
      return { user: null };
    }

    const payload = await jwt.verify(token);
    return { user: payload };
  })
  .macro(({ onBeforeHandle }) => ({
    requireAuth(enabled: boolean) {
      if (!enabled) return;
      onBeforeHandle(({ user, error }) => {
        if (!user) return error(401, 'Yetkilendirme gerekli');
      });
    }
  }));
// routes/posts.ts
import { Elysia, t } from 'elysia';
import { authPlugin } from '../plugins/auth';

export const postsRoutes = new Elysia({ prefix: '/api/posts' })
  .use(authPlugin)

  // Herkese açık endpoint
  .get('/', () => {
    return { posts: [{ id: 1, title: 'İlk Yazı' }] };
  })

  // Korumalı endpoint
  .post(
    '/',
    ({ body, user }) => {
      return {
        message: 'Yazı oluşturuldu',
        post: { ...body, author: user?.sub }
      };
    },
    {
      requireAuth: true,
      body: t.Object({
        title: t.String({ minLength: 3 }),
        content: t.String({ minLength: 10 })
      })
    }
  );
// src/index.ts
import { Elysia } from 'elysia';
import { swagger } from '@elysiajs/swagger';
import { cors } from '@elysiajs/cors';
import { postsRoutes } from './routes/posts';

const app = new Elysia()
  .use(swagger({
    documentation: {
      info: {
        title: 'My API',
        version: '1.0.0',
        description: 'ElysiaJS ile geliştirilmiş ultra-hızlı API'
      }
    }
  }))
  .use(cors())
  .use(postsRoutes)
  .get('/', () => ({ message: 'API çalışıyor!' }))
  .listen(3000);

console.log('🦊 API hazır: http://localhost:3000');
console.log('📚 Swagger UI: http://localhost:3000/swagger');

Gerekli plugin'leri kurmayı unutmayın:

bun add @elysiajs/swagger @elysiajs/cors @elysiajs/jwt

Hata Yönetimi

ElysiaJS, yapılandırılmış hata yönetimi için zarif bir API sunar:

import { Elysia, t, NotFoundError } from 'elysia';

class BusinessError extends Error {
  constructor(
    public statusCode: number,
    message: string
  ) {
    super(message);
  }
}

const app = new Elysia()
  // Global hata yakalayıcı
  .onError(({ code, error, set }) => {
    if (error instanceof BusinessError) {
      set.status = error.statusCode;
      return {
        success: false,
        error: error.message,
        code: error.statusCode
      };
    }

    switch (code) {
      case 'NOT_FOUND':
        set.status = 404;
        return { success: false, error: 'Kaynak bulunamadı' };

      case 'VALIDATION':
        set.status = 422;
        return { success: false, error: 'Validasyon hatası', details: error.message };

      case 'INTERNAL_SERVER_ERROR':
        set.status = 500;
        return { success: false, error: 'Sunucu hatası' };

      default:
        return { success: false, error: 'Bilinmeyen hata' };
    }
  })

  .get('/api/products/:id', ({ params: { id } }) => {
    if (Number(id) > 100) {
      throw new BusinessError(404, 'Bu ID ile ürün bulunamadı');
    }

    if (Number(id) < 0) {
      throw new BusinessError(400, 'Geçersiz ürün ID');
    }

    return { id: Number(id), name: 'Örnek Ürün', price: 99.99 };
  })

  .listen(3000);

Veritabanı Entegrasyonu: Drizzle ORM Örneği

Gerçek bir projede veritabanı entegrasyonu şarttır. ElysiaJS, herhangi bir ORM ile sorunsuz çalışır. İşte Drizzle ORM ile bir SQLite örneği:

bun add drizzle-orm drizzle-kit better-sqlite3
bun add -d @types/better-sqlite3
// db/schema.ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  createdAt: text('created_at').default('CURRENT_TIMESTAMP')
});

export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
// db/index.ts
import { drizzle } from 'drizzle-orm/bun-sqlite';
import { Database } from 'bun:sqlite';
import * as schema from './schema';

const sqlite = new Database('myapp.db');
export const db = drizzle(sqlite, { schema });
// routes/users.ts
import { Elysia, t } from 'elysia';
import { db } from '../db';
import { users } from '../db/schema';
import { eq } from 'drizzle-orm';

export const userRoutes = new Elysia({ prefix: '/api/users' })
  .get('/', async () => {
    const allUsers = await db.select().from(users);
    return { data: allUsers, count: allUsers.length };
  })

  .get('/:id', async ({ params: { id }, error }) => {
    const user = await db.select().from(users).where(eq(users.id, Number(id)));
    if (!user.length) return error(404, 'Kullanıcı bulunamadı');
    return { data: user[0] };
  })

  .post(
    '/',
    async ({ body }) => {
      const result = await db.insert(users).values(body).returning();
      return { message: 'Oluşturuldu', data: result[0] };
    },
    {
      body: t.Object({
        name: t.String({ minLength: 2 }),
        email: t.String({ format: 'email' })
      })
    }
  );

Eden Treaty: Frontend ile Tam Tip Entegrasyonu

ElysiaJS'in en etkileyici özelliklerinden biri Eden Treaty'dir. Backend'deki tipleri otomatik olarak frontend'e taşır — tıpkı tRPC gibi, ama REST API üzerinde:

bun add @elysiajs/eden
// Frontend tarafı (React, Next.js, vb.)
import { treaty } from '@elysiajs/eden';
import type { App } from '../server'; // Backend'den tip import'u

const api = treaty<App>('http://localhost:3000');

// Tam otomatik tamamlama ve tip güvenliği!
const { data, error } = await api.api.users.get();

// data otomatik olarak User[] tipinde
if (data) {
  data.forEach(user => {
    console.log(user.name); // TypeScript biliyor ki bu string
  });
}

// POST isteği - body tipi otomatik olarak kontrol edilir
const { data: newUser } = await api.api.users.post({
  name: 'Yeni Kullanıcı',
  email: 'yeni@example.com'
});

Performans Karşılaştırması

Aşağıdaki tablo, farklı framework'lerin saniyedeki istek sayısını (requests/second) göstermektedir (basit JSON response benchmark'ı):

Framework Runtime req/sec
ElysiaJS Bun ~320.000
Hono Bun ~280.000
Fastify Node.js ~78.000
Koa Node.js ~45.000
Express.js Node.js ~18.000

Not: Benchmark sonuçları donanım, test koşulları ve payload boyutuna göre değişiklik gösterebilir. Bu veriler genel bir fikir vermesi amaçlıdır.

ElysiaJS'in bu performansa ulaşmasının arkasındaki ana faktörler:


Production Checklist

Projenizi production'a almadan önce kontrol etmeniz gereken maddeler:

// production-ready src/index.ts
import { Elysia } from 'elysia';
import { swagger } from '@elysiajs/swagger';
import { cors } from '@elysiajs/cors';
import { helmet } from 'elysia-helmet';
import { rateLimit } from 'elysia-rate-limit';

const app = new Elysia()
  .use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'] }))
  .use(helmet())
  .use(rateLimit({ max: 100, duration: 60000 }))
  .use(swagger({ path: '/docs' }))

  // İstek loglama
  .onRequest(({ request }) => {
    console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`);
  })

  // Graceful shutdown
  .listen({
    port: Number(process.env.PORT) || 3000,
    hostname: '0.0.0.0'
  });

process.on('SIGTERM', () => {
  console.log('Sunucu kapatılıyor...');
  app.stop();
  process.exit(0);
});

Sonuç

ElysiaJS ve Bun ikilisi, TypeScript ile backend geliştirme deneyimini tamamen yeni bir seviyeye taşıyor. Bu yazıda incelediğimiz temel noktaları özetleyelim:

Eğer yeni bir API projesi başlıyorsanız veya mevcut Node.js projelerinizin performans sınırlarını zorluyorsanız, ElysiaJS + Bun kombinasyonunu kesinlikle değerlendirmelisiniz. Özellikle TypeScript'e olan doğal uyumu ve sıfır konfigürasyon felsefesi, geliştirici deneyimini üst düzeye çıkarıyor.

Başlamak için tek yapmanız gereken:

bun create elysia my-next-project

Hızlı geliştirmeler! 🦊


Share this post on:

Sonraki Yazı
Zod v4 Neler Getirdi? Bundle Boyutu, Performans ve Yeni API Devrimi