Web Development

Bun dan Elysia.js: High Performance API

Bangun API ultra-cepat dengan Bun runtime dan Elysia.js framework β€” type safety penuh, middleware yang elegan, dan performa yang menghancurkan Node.js. Lengkap dengan benchmark dan studi kasus.

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?

FiturBunElysia
Kecepatan3-5x lebih cepat dari Node.jsFaster than Express/Fastify
Type SafetyTypeScript nativeEnd-to-end tanpa codegen
Package Managerbun install β€” 20x lebih cepat dari npmPlugin system yang fleksibel
Built-inBundler, test runner, SQLiteWebSocket, SSE, CORS, Swagger
CompatibilityNode.js compatibleEden Treaty untuk client

Arsitektur Bun Runtime

Diagram: Bun vs Node.js Architecture
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            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

Shell β€” Install 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
Shell β€” Membuat Project Elysia
# 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

TypeScript β€” Hello World 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}`
);
TypeScript β€” Elysia dengan Plugin
// 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.

TypeScript β€” CRUD Routes
// 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" };
  });
TypeScript β€” Query Parameters & Grouping
// 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.

TypeScript β€” Lifecycle Hooks & Middleware
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)

TypeScript β€” Auth Plugin
// 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.

TypeScript β€” Server-side Type Export
// 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;
TypeScript β€” Client-side dengan Eden Treaty
// 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
πŸ’‘ Tips

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.

TypeScript β€” Schema Validation
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:

FrameworkRuntimeRequests/secLatency (avg)
ElysiaBun~280,0000.03ms
HonoBun~250,0000.04ms
FastifyNode.js~95,0000.10ms
ExpressNode.js~35,0000.28ms
HonoDeno~200,0000.05ms
⚠️ Catatan Benchmark

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.

Shell β€” Menjalankan Benchmark Sendiri
# 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

βœ… Bun + Elysia 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 onError untuk 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:

Pertanyaan 1: Bahasa apa yang digunakan untuk membangun inti (core) Bun runtime?

a) Rust
b) C++
c) Zig
d) Go

Pertanyaan 2: Apa fitur unggulan Elysia yang memberikan type safety tanpa code generation?

a) OpenAPI
b) GraphQL
c) Eden Treaty
d) gRPC

Pertanyaan 3: Apa yang digunakan Elysia untuk validasi schema?

a) Zod
b) Joi
c) Yup
d) TypeBox

Pertanyaan 4: Mesin JavaScript apa yang digunakan Bun?

a) V8 (Chrome)
b) JavaScriptCore (WebKit/Safari)
c) SpiderMonkey (Firefox)
d) Hermes (React Native)

Pertanyaan 5: Perintah apa yang digunakan untuk membuat project Elysia baru?

a) npm init elysia
b) bun create elysia
c) deno create elysia
d) npx create-elysia
πŸ” Zoom
100%
🎨 Tema