1. Pengenalan Bun & Elysia
Bun adalah JavaScript runtime all-in-one yang dibangun dari nol dengan Zig dan JavaScriptCore (mesin WebKit). Bun mengklaim sebagai runtime tercepat untuk JavaScript β mengungguli Node.js dan Deno di banyak benchmark. Bun mencakup bundler, test runner, dan package manager built-in.
Elysia.js adalah web framework berperforma tinggi yang dibangun khusus untuk Bun. Elysia menawarkan API yang elegan, type safety end-to-end (tanpa code generation), dan dukungan penuh untuk TypeScript. Filosofinya: "Kode yang Anda tulis adalah kode yang Anda dapatkan."
Mengapa Bun + Elysia?
| Fitur | Bun | Elysia |
|---|---|---|
| Kecepatan | 3-5x lebih cepat dari Node.js | Faster than Express/Fastify |
| Type Safety | TypeScript native | End-to-end tanpa codegen |
| Package Manager | bun install β 20x lebih cepat dari npm | Plugin system yang fleksibel |
| Built-in | Bundler, test runner, SQLite | WebSocket, SSE, CORS, Swagger |
| Compatibility | Node.js compatible | Eden Treaty untuk client |
Arsitektur Bun Runtime
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β ARSITEKTUR RUNTIME COMPARISON β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β β β Node.js: β β ββββββββββββ ββββββββββββ ββββββββββββββββββββββββ β β β V8 ββββ libuv ββββ C++ bindings (fs,net) β β β β Engine β β Event β β β β β β β β Loop β ββββββββββββββββββββββββ β β ββββββββββββ ββββββββββββ β β β β Bun: β β ββββββββββββ ββββββββββββ ββββββββββββββββββββββββ β β βJavaScriptββββ Zig ββββ Built-in APIs β β β βCore(WebKitβ β Core β β (fs,net,sqlite,dns) β β β β Engine) β β Modules β β β β β ββββββββββββ ββββββββββββ ββββββββββββββββββββββββ β β β β Keunggulan Bun: Zig core β memory-safe & ultra cepat β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Instalasi Bun
# macOS / Linux / WSL curl -fsSL https://bun.sh/install | bash # Windows (PowerShell) powershell -c "irm bun.sh/install.ps1 | iex" # Via npm (jika sudah punya Node.js) npm install -g bun # Verifikasi bun --version # 1.1.x # Bun sebagai package manager (drop-in replacement npm) bun install # Menggantikan npm install (20x lebih cepat) bun add express # Menggantikan npm install express bun add -d typescript # Menggantikan npm install -D typescript # Bun sebagai test runner bun test # Menggantikan jest/mocha
# Membuat project baru dengan template Elysia
bun create elysia my-api
cd my-api
# Atau manual:
mkdir my-api && cd my-api
bun init -y
bun add elysia @elysiajs/cors @elysiajs/swagger
# Jalankan development server
bun run src/index.ts
# Server berjalan di http://localhost:3000
# File: package.json
# {
# "name": "my-api",
# "scripts": {
# "dev": "bun run --watch src/index.ts",
# "start": "bun run src/index.ts"
# }
# }
3. Memulai dengan Elysia
// src/index.ts
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => "Hello, Elysia! π¦")
.get("/json", () => ({
message: "Ini JSON response",
timestamp: Date.now(),
}))
.listen(3000);
console.log(
`π¦ Elysia server running at http://${app.server?.hostname}:${app.server?.port}`
);
// src/index.ts
import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { swagger } from "@elysiajs/swagger";
const app = new Elysia()
.use(cors()) // CORS middleware
.use(swagger()) // Swagger docs di /swagger
.get("/", () => "Hello World")
.get("/health", () => ({
status: "ok",
uptime: process.uptime(),
memory: process.memoryUsage(),
}))
.listen(3000);
export type App = typeof app; // Export type untuk client
4. Routes & HTTP Methods
Elysia mendukung semua HTTP method dengan syntax yang sangat bersih dan type-safe.
// src/routes/users.ts
import { Elysia, t } from "elysia";
// Simulasi database
const users = [
{ id: 1, name: "Budi", email: "budi@mail.com" },
{ id: 2, name: "Ani", email: "ani@mail.com" },
];
export const userRoutes = new Elysia({ prefix: "/api/users" })
// GET /api/users β Ambil semua user
.get("/", () => users)
// GET /api/users/:id β Ambil user by ID
.get("/:id", ({ params: { id } }) => {
const user = users.find((u) => u.id === Number(id));
if (!user) {
return new Response(JSON.stringify({ error: "User tidak ditemukan" }), {
status: 404,
});
}
return user;
})
// POST /api/users β Buat user baru
.post(
"/",
({ body }) => {
const newUser = { id: users.length + 1, ...body };
users.push(newUser);
return { success: true, user: newUser };
},
{
body: t.Object({
name: t.String({ minLength: 2 }),
email: t.String({ format: "email" }),
}),
}
)
// PUT /api/users/:id β Update user
.put(
"/:id",
({ params: { id }, body }) => {
const index = users.findIndex((u) => u.id === Number(id));
if (index === -1) {
return new Response(JSON.stringify({ error: "User tidak ditemukan" }), {
status: 404,
});
}
users[index] = { ...users[index], ...body };
return { success: true, user: users[index] };
},
{
body: t.Object({
name: t.Optional(t.String()),
email: t.Optional(t.String({ format: "email" })),
}),
}
)
// DELETE /api/users/:id β Hapus user
.delete("/:id", ({ params: { id } }) => {
const index = users.findIndex((u) => u.id === Number(id));
if (index === -1) {
return new Response(JSON.stringify({ error: "User tidak ditemukan" }), {
status: 404,
});
}
users.splice(index, 1);
return { success: true, message: "User dihapus" };
});
// src/index.ts β Menggabungkan routes
import { Elysia } from "elysia";
import { userRoutes } from "./routes/users";
import { postRoutes } from "./routes/posts";
const app = new Elysia()
.use(userRoutes)
.use(postRoutes)
// Query parameters
.get("/search", ({ query }) => {
const { q, page = "1", limit = "10" } = query;
return {
query: q,
page: Number(page),
limit: Number(limit),
results: [], // Hasil pencarian
};
})
// Pattern matching wildcard
.get("/files/*", ({ params }) => {
return { path: params["*"] };
})
.listen(3000);
5. Middleware & Lifecycle
Elysia memiliki lifecycle hooks yang memungkinkan Anda menyisipkan logika pada berbagai tahap request lifecycle β sebelum request diproses, setelah response dibuat, dan saat error terjadi.
import { Elysia } from "elysia";
const app = new Elysia()
// onRequest β dijalankan untuk setiap request
.onRequest(({ request }) => {
console.log(`β ${request.method} ${new URL(request.url).pathname}`);
})
// onBeforeHandle β sebelum handler dipanggil
.onBeforeHandle(({ request, set }) => {
// Auth middleware
const token = request.headers.get("Authorization");
if (new URL(request.url).pathname.startsWith("/api/protected")) {
if (!token || !token.startsWith("Bearer ")) {
set.status = 401;
return { error: "Unauthorized" };
}
}
})
// onAfterHandle β setelah handler mengembalikan response
.onAfterHandle(({ response, set }) => {
// Tambahkan custom headers
set.headers["X-Powered-By"] = "Elysia";
set.headers["X-Response-Time"] = `${Date.now()}ms`;
return response;
})
// onError β menangani error
.onError(({ error, code, set }) => {
console.error(`Error [${code}]:`, error.message);
switch (code) {
case "NOT_FOUND":
set.status = 404;
return { error: "Endpoint tidak ditemukan" };
case "VALIDATION":
set.status = 422;
return { error: "Validasi gagal", details: error.message };
case "INTERNAL_SERVER_ERROR":
set.status = 500;
return { error: "Server error internal" };
}
})
.get("/", () => "Hello World")
.get("/api/protected/data", () => ({ secret: "data rahasia" }))
.listen(3000);
Reusable Plugin (Middleware Modular)
// src/plugins/auth.ts
import { Elysia } from "elysia";
interface AuthConfig {
secret: string;
exclude?: string[];
}
export const authPlugin = (config: AuthConfig) =>
new Elysia({ name: "auth" })
.derive(({ request }) => {
const token = request.headers.get("Authorization")?.replace("Bearer ", "");
return {
user: token ? verifyToken(token, config.secret) : null,
};
})
.onBeforeHandle(({ request, set, user }) => {
const path = new URL(request.url).pathname;
// Skip auth untuk excluded paths
if (config.exclude?.some((p) => path.startsWith(p))) return;
if (!user) {
set.status = 401;
return { error: "Silakan login terlebih dahulu" };
}
});
// Penggunaan
const app = new Elysia()
.use(
authPlugin({
secret: process.env.JWT_SECRET ?? "my-secret",
exclude: ["/api/public", "/health"],
})
)
.get("/api/profile", ({ user }) => user)
.listen(3000);
function verifyToken(token: string, secret: string) {
// Verifikasi JWT (contoh sederhana)
return { id: "1", name: "Budi", email: "budi@mail.com" };
}
6. Type Safety dengan Eden
Eden Treaty adalah fitur unggulan Elysia yang memungkinkan type-safe API calls dari klien β tanpa code generation, tanpa OpenAPI spec. TypeScript langsung menginfer type dari definisi route server.
// src/index.ts β Server
import { Elysia, t } from "elysia";
const app = new Elysia()
.get("/api/users", () => [
{ id: 1, name: "Budi", email: "budi@mail.com" },
{ id: 2, name: "Ani", email: "ani@mail.com" },
])
.post(
"/api/users",
({ body }) => ({
success: true,
user: { id: 3, ...body },
}),
{
body: t.Object({
name: t.String(),
email: t.String({ format: "email" }),
}),
}
)
.listen(3000);
// Export type Elysia
export type App = typeof app;
// client.ts β Client code
import { treaty } from "@elysiajs/eden";
import type { App } from "./src/index";
// Buat client dengan type safety penuh
const api = treaty<App>("localhost:3000");
// GET /api/users β TypeScript tahu return type-nya!
const { data: users, error } = await api.api.users.get();
if (users) {
users.forEach((user) => {
// TypeScript tahu: user.name (string), user.email (string)
console.log(`${user.name} (${user.email})`);
});
}
// POST /api/users β Type-safe body!
const { data: result } = await api.api.users.post({
name: "Sari",
email: "sari@mail.com",
});
// TypeScript tahu: result.success (boolean), result.user (object)
// Autocomplete berfungsi sempurna:
// api. β menunjukkan semua endpoint yang tersedia
// api.api.users.post({ β menunjukkan field yang dibutuhkan
Eden Treaty benar-benar zero-runtime-cost. Tidak ada runtime validation atau code generation β semua type safety terjadi di compile time. Ini artinya Anda mendapatkan DX yang luar biasa tanpa mengorbankan performa.
7. Validation & Schema
Elysia menggunakan TypeBox (via t) untuk validasi built-in yang juga berfungsi sebagai TypeScript type inference. Ini berarti satu definisi schema berfungsi sebagai validasi runtime DAN type static.
import { Elysia, t } from "elysia";
const app = new Elysia()
// Validasi body, params, dan query sekaligus
.post(
"/api/orders",
({ body, query }) => {
// body dan query sudah ter-validasi dan ter-typed
return {
orderId: Math.random().toString(36).slice(2),
items: body.items,
page: query.page,
};
},
{
// Validasi request body
body: t.Object({
customerName: t.String({ minLength: 2, maxLength: 100 }),
email: t.String({ format: "email" }),
items: t.Array(
t.Object({
productId: t.String(),
quantity: t.Number({ minimum: 1, maximum: 999 }),
price: t.Number({ minimum: 0 }),
}),
{ minItems: 1, maxItems: 50 }
),
shippingAddress: t.Object({
street: t.String(),
city: t.String(),
province: t.String(),
postalCode: t.String({ pattern: "^[0-9]{5}$" }),
}),
paymentMethod: t.Union([
t.Literal("credit_card"),
t.Literal("bank_transfer"),
t.Literal("ewallet"),
]),
notes: t.Optional(t.String({ maxLength: 500 })),
}),
// Validasi query parameters
query: t.Object({
page: t.Optional(t.Numeric({ minimum: 1 })),
sort: t.Optional(
t.Union([t.Literal("newest"), t.Literal("cheapest")])
),
}),
// Validasi response (documentation)
response: {
200: t.Object({
orderId: t.String(),
items: t.Array(t.Any()),
page: t.Number(),
}),
422: t.Object({
error: t.String(),
details: t.Any(),
}),
},
}
)
.listen(3000);
8. Benchmark & Performance
Bun + Elysia secara konsisten menghasilkan performa terbaik di antara JavaScript/TypeScript web framework. Berikut perbandingan benchmark:
| Framework | Runtime | Requests/sec | Latency (avg) |
|---|---|---|---|
| Elysia | Bun | ~280,000 | 0.03ms |
| Hono | Bun | ~250,000 | 0.04ms |
| Fastify | Node.js | ~95,000 | 0.10ms |
| Express | Node.js | ~35,000 | 0.28ms |
| Hono | Deno | ~200,000 | 0.05ms |
Benchmark di atas menggunakan skenario "hello world" sederhana. Performa di production akan bervariasi tergantung kompleksitas bisnis logic, database queries, dan network I/O. Yang penting bukan absolut request/detik, tapi overhead framework yang lebih kecil.
# Install benchmarking tool (wrk) # macOS: brew install wrk # Linux: sudo apt install wrk # Benchmark Elysia wrk -t12 -c400 -d30s http://localhost:3000/ # Benchmark dengan autocannon (Node.js) npx autocannon -c 100 -d 30 http://localhost:3000/ # Hasil contoh: # Running 30s test @ http://localhost:3000/ # 12 threads and 400 connections # Thread Stats Avg Stdev Max +/- Stdev # Latency 0.03ms 0.01ms 0.50ms 95.00% # Req/Sec 23,333 1,200.00 25,000 80.00% # 840,000 requests in 30.00s
9. Best Practices
- Gunakan plugin system untuk middleware modular dan reusable
- TypeBox schemas untuk validasi body/query β satu definisi untuk validasi + type
- Eden Treaty untuk client communication β zero-cost type safety
- Prefix grouping β gunakan
new Elysia({ prefix: "/api/..." }) - Error boundaries β gunakan
onErroruntuk error handling konsisten - bun:sqlite β gunakan built-in SQLite untuk data lokal
- bun:test β gunakan built-in test runner
- Graceful shutdown β tangani SIGTERM untuk cleanup
- Environment variables β Bun mendukung .env secara native
- Monitoring β tambahkan logging di lifecycle hooks
10. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial Bun dan Elysia, jawablah 5 pertanyaan berikut: