Elysia.js ile End-to-End Tip Güvenliği: Full-Stack TypeScript'in Geleceği
Modern web geliştirmede en sinir bozucu hatalardan biri, backend'in döndüğü verinin frontend'de beklediğimiz yapıyla uyuşmamasıdır. API'den gelen response'un tipini elle tanımlamak, zamanla senkronizasyon sorunlarına ve runtime hatalarına yol açar. Peki ya backend'de tanımladığınız tipler otomatik olarak frontend'e taşınabilseydi?
İşte tam bu noktada Elysia.js devreye giriyor. Bun runtime üzerinde çalışan bu framework, TypeScript'in tip sistemini sonuna kadar kullanarak end-to-end (uçtan uca) tip güvenliği sağlar. Bu yazıda Elysia.js'in ne olduğunu, nasıl çalıştığını ve Eden Treaty ile frontend-backend arasında nasıl sıfır boşluklu bir tip köprüsü kurabileceğinizi derinlemesine inceleyeceğiz.
Elysia.js Nedir?
Elysia.js, Bun runtime üzerinde çalışmak üzere tasarlanmış, yüksek performanslı bir TypeScript web framework'üdür. Express.js veya Fastify'a alternatif olarak konumlanır ancak onlardan çok farklı bir yaklaşım benimser: tip güvenliğini birinci sınıf vatandaş olarak ele alır.
Neden Elysia.js?
- Olağanüstü performans: Bun'un hızlı HTTP sunucusu üzerine inşa edilmiştir
- End-to-end tip güvenliği: Eden Treaty ile frontend'e otomatik tip aktarımı
- Ergonomik API: Zincirlenebilir (chainable) ve deklaratif söz dizimi
- Doğrulama entegrasyonu: TypeBox ile built-in şema doğrulaması
- Plugin ekosistemi: Swagger, CORS, JWT gibi hazır eklentiler
Kurulum ve İlk Adımlar
Öncelikle Bun'un sisteminizde kurulu olduğundan emin olun:
curl -fsSL https://bun.sh/install | bashYeni bir Elysia projesi oluşturalım:
bun create elysia my-app
cd my-app
bun installİlk Elysia sunucunuz şu kadar basit:
// src/index.ts
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/', () => 'Merhaba Dünya!')
.get('/health', () => ({ status: 'ok', timestamp: Date.now() }))
.listen(3000)
console.log(`🦊 Elysia sunucusu http://localhost:${app.server?.port} adresinde çalışıyor`)bun run src/index.tsBu basit örnekte bile Elysia, döndürdüğünüz değerlerin tiplerini otomatik olarak çıkarsıyor. '/health' endpoint'i { status: string, timestamp: number } tipinde bir response döndüğünü biliyor.
Tip Güvenliğinin Temeli: Route Şemaları
Elysia.js'in gerçek gücü, route'lara şema tanımlamaları eklediğinizde ortaya çıkar. Bu şemalar hem runtime doğrulaması hem de derleme zamanı tip güvenliği sağlar.
Request Body Doğrulama
import { Elysia, t } from 'elysia'
const app = new Elysia()
.post('/users', ({ body }) => {
// body otomatik olarak { name: string, email: string, age: number } tipindedir
return {
id: crypto.randomUUID(),
name: body.name,
email: body.email,
age: body.age,
createdAt: new Date().toISOString()
}
}, {
body: t.Object({
name: t.String({ minLength: 2 }),
email: t.String({ format: 'email' }),
age: t.Number({ minimum: 18 })
})
})
.listen(3000)Burada t (TypeBox) ile tanımladığınız şema iki işlevi aynı anda yerine getirir:
- Runtime: Gelen isteğin gövdesini doğrular; uyumsuzlukta otomatik 422 hatası döner
- Compile-time:
bodyparametresinin tipini TypeScript'e bildirir; IDE'nizde otomatik tamamlama çalışır
Query ve Params Doğrulama
const app = new Elysia()
.get('/users/:id', ({ params, query }) => {
// params.id -> string
// query.includeProfile -> boolean (opsiyonel)
return {
userId: params.id,
includeProfile: query.includeProfile
}
}, {
params: t.Object({
id: t.String({ format: 'uuid' })
}),
query: t.Object({
includeProfile: t.Optional(t.Boolean({ default: false }))
})
})Response Tipini Açıkça Tanımlama
Response tipini de şema ile kısıtlayabilirsiniz. Bu, API kontratınızı daha da sağlam hale getirir:
const app = new Elysia()
.post('/login', ({ body }) => {
// Eğer burada response şemasına uymayan bir değer dönerseniz
// TypeScript derleme zamanında hata verir!
return {
token: 'jwt-token-here',
expiresIn: 3600
}
}, {
body: t.Object({
email: t.String({ format: 'email' }),
password: t.String({ minLength: 8 })
}),
response: t.Object({
token: t.String(),
expiresIn: t.Number()
})
})Eden Treaty: Frontend ile Tip Köprüsü
Elysia.js'in en devrimci özelliği Eden Treaty isimli istemci kütüphanesidir. Eden, backend'deki Elysia uygulamanızın tip bilgisini frontend'e otomatik olarak taşır. Hiçbir kod üretimi (code generation) veya ek adım gerekmez.
Nasıl Çalışır?
Eden Treaty, TypeScript'in tip çıkarımı (type inference) mekanizmasını kullanır. Elysia uygulamanızın tipini export edersiniz ve Eden bu tipi alarak tüm endpoint'leriniz için tip güvenli bir istemci oluşturur.
Adım 1: Backend'de Tip Export Etme
// server/src/index.ts
import { Elysia, t } from 'elysia'
const app = new Elysia()
.get('/posts', () => {
return [
{ id: 1, title: 'Elysia.js Rehberi', views: 1500 },
{ id: 2, title: 'Bun ile Performans', views: 2300 }
]
})
.get('/posts/:id', ({ params }) => {
return {
id: parseInt(params.id),
title: 'Elysia.js Rehberi',
content: 'Detaylı içerik burada...',
views: 1500,
author: { name: 'Ahmet', avatar: '/avatars/ahmet.png' }
}
}, {
params: t.Object({
id: t.String()
})
})
.post('/posts', ({ body }) => {
return {
id: 3,
...body,
views: 0,
createdAt: new Date().toISOString()
}
}, {
body: t.Object({
title: t.String({ minLength: 5 }),
content: t.String({ minLength: 20 })
})
})
.listen(3000)
// ✅ Uygulamanın tipini export edin
export type App = typeof appAdım 2: Frontend'de Eden Treaty Kurulumu
bun add @elysiajs/edenAdım 3: Tip Güvenli API Çağrıları
// client/src/api.ts
import { treaty } from '@elysiajs/eden'
import type { App } from '../../server/src/index'
// Tip güvenli istemci oluştur
const api = treaty<App>('http://localhost:3000')
// ✅ Tüm endpoint'ler otomatik tamamlama ile gelir
async function fetchPosts() {
const { data, error } = await api.posts.get()
if (error) {
console.error('Hata:', error)
return
}
// data tipi otomatik olarak:
// { id: number, title: string, views: number }[]
data.forEach(post => {
console.log(post.title) // ✅ Otomatik tamamlama çalışır
console.log(post.views) // ✅ number olarak tanınır
// console.log(post.foo) // ❌ TypeScript hatası verir!
})
}
async function fetchPostById(id: string) {
const { data, error } = await api.posts({ id }).get()
if (data) {
// data.author.name -> string ✅
// data.content -> string ✅
console.log(`${data.title} - ${data.author.name}`)
}
}
async function createPost() {
const { data, error } = await api.posts.post({
title: 'Yeni Yazı Başlığı',
content: 'Bu yazının içeriği en az 20 karakter olmalı.'
// age: 25 // ❌ TypeScript hatası: 'age' şemada yok
})
if (data) {
console.log(`Post oluşturuldu: ${data.id} - ${data.createdAt}`)
}
}Bu yaklaşımın güzelliği: backend'de bir şeyi değiştirdiğinizde, frontend'de anında TypeScript hatası alırsınız. Artık "API response yapısı değişmiş ama frontend hâlâ eski yapıyı bekliyor" gibi bug'lar tarihe karışıyor.
React ile Elysia Eden Entegrasyonu
Eden Treaty'yi bir React uygulamasında nasıl kullanacağınıza bakalım:
// client/src/hooks/useApi.ts
import { treaty } from '@elysiajs/eden'
import type { App } from '../../../server/src/index'
import { useState, useEffect } from 'react'
export const api = treaty<App>('http://localhost:3000')
// Tip güvenli custom hook
export function usePosts() {
const [posts, setPosts] = useState<Awaited<ReturnType<typeof api.posts.get>>['data']>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function load() {
const { data, error } = await api.posts.get()
if (error) {
setError('Postlar yüklenirken hata oluştu')
} else {
setPosts(data)
}
setLoading(false)
}
load()
}, [])
return { posts, loading, error }
}// client/src/components/PostList.tsx
import { usePosts } from '../hooks/useApi'
export function PostList() {
const { posts, loading, error } = usePosts()
if (loading) return <div>Yükleniyor...</div>
if (error) return <div className="error">{error}</div>
return (
<ul>
{posts?.map(post => (
<li key={post.id}>
<h3>{post.title}</h3>
<span>{post.views} görüntülenme</span>
</li>
))}
</ul>
)
}Gruplandırma ve Modüler Yapı
Gerçek projelerde tüm route'ları tek bir dosyaya yazmak sürdürülebilir değildir. Elysia, plugin sistemi ile modüler yapıyı destekler ve tip güvenliğini korur:
// server/src/modules/auth.ts
import { Elysia, t } from 'elysia'
export const authModule = new Elysia({ prefix: '/auth' })
.post('/register', ({ body }) => {
return { id: crypto.randomUUID(), email: body.email, role: 'user' as const }
}, {
body: t.Object({
email: t.String({ format: 'email' }),
password: t.String({ minLength: 8 }),
name: t.String()
})
})
.post('/login', ({ body }) => {
return { token: 'jwt-token', refreshToken: 'refresh-token' }
}, {
body: t.Object({
email: t.String({ format: 'email' }),
password: t.String()
})
})// server/src/modules/posts.ts
import { Elysia, t } from 'elysia'
export const postsModule = new Elysia({ prefix: '/posts' })
.get('/', () => {
return [{ id: 1, title: 'İlk Yazı', slug: 'ilk-yazi' }]
})
.post('/', ({ body }) => {
return { id: 2, ...body, createdAt: new Date() }
}, {
body: t.Object({
title: t.String(),
content: t.String()
})
})// server/src/index.ts
import { Elysia } from 'elysia'
import { authModule } from './modules/auth'
import { postsModule } from './modules/posts'
const app = new Elysia()
.use(authModule)
.use(postsModule)
.listen(3000)
// Tüm modüllerin tipleri birleştirilir
export type App = typeof appFrontend tarafında artık api.auth.register.post(...) ve api.posts.index.get() gibi çağrıların hepsi tip güvenlidir.
tRPC ile Karşılaştırma
End-to-end tip güvenliği denince akla ilk gelen tRPC'dir. İkisini kısaca karşılaştıralım:
| Özellik | Elysia.js + Eden | tRPC |
|---|---|---|
| Runtime | Bun (öncelikli) | Node.js / Bun |
| Protokol | Standart REST | RPC (özel protokol) |
| Performans | Çok yüksek | İyi |
| Öğrenme eğrisi | Düşük | Orta |
| REST uyumluluğu | Doğal | Yok (adapter gerekir) |
| Swagger/OpenAPI | Built-in plugin | Ek kütüphane |
| Frontend agnostik | Evet | React/Next.js odaklı |
Elysia.js'in en büyük avantajı standart REST endpoint'leri kullanmasıdır. Bu, API'nizi sadece TypeScript istemcileri değil, mobil uygulamalar veya üçüncü parti servisler de tüketebilir demektir. Aynı zamanda Swagger dokümantasyonu otomatik üretilebilir.
Hata Yönetimi ve Tip Güvenliği
Elysia, hata durumlarını da tipli bir şekilde yönetmenize olanak tanır:
import { Elysia, t, error } from 'elysia'
const app = new Elysia()
.get('/users/:id', ({ params }) => {
const user = findUser(params.id)
if (!user) {
return error(404, {
message: 'Kullanıcı bulunamadı',
code: 'USER_NOT_FOUND'
})
}
return user
}, {
params: t.Object({ id: t.String() }),
response: {
200: t.Object({
id: t.String(),
name: t.String(),
email: t.String()
}),
404: t.Object({
message: t.String(),
code: t.String()
})
}
})Eden tarafında bu hata tipleri de korunur:
const { data, error } = await api.users({ id: '123' }).get()
if (error) {
// error.value tipi: { message: string, code: string }
switch (error.status) {
case 404:
console.log(error.value.code) // 'USER_NOT_FOUND'
break
}
}Sonuç
Elysia.js, TypeScript ekosisteminde end-to-end tip güvenliği konusunda en ergonomik çözümlerden birini sunuyor. Özetlemek gerekirse:
- Backend'de
t(TypeBox) ile şemalarınızı tanımlayarak hem runtime doğrulaması hem derleme zamanı güvenliği elde edersiniz - Eden Treaty ile backend tiplerini frontend'e sıfır yapılandırmayla aktarırsınız
- Standart REST kullanıldığı için API'niz evrensel olarak tüketilebilir
- Bun runtime sayesinde olağanüstü performans alırsınız
- Modüler yapı ile büyük projelerde bile tip güvenliği kaybolmaz
Eğer yeni bir full-stack TypeScript projesi başlıyorsanız veya mevcut API'nizde tip güvenliği sorunları yaşıyorsanız, Elysia.js kesinlikle değerlendirmeniz gereken bir framework. Backend'den frontend'e uzanan kesintisiz tip zinciri, geliştirici deneyiminizi ve uygulamanızın güvenilirliğini bir üst seviyeye taşıyacaktır.