Bun
Pendahuluan
Bun adalah runtime JavaScript yang sangat cepat, dirancang sebagai pengganti langsung (drop-in replacement) untuk Node.js.
Fitur Utama Bun
- Bun Runtime: Lingkungan eksekusi JavaScript/TypeScript yang cepat dengan overhead mendekati nol, kompatibel dengan API Web standar (
fetch,WebSocket,ReadableStream) dan modul Node.js (CommonJS & ESM). ^jduOOBC9 - Bun Package Manager
bun install: package manager yang diklaim hingga 30 kali lebih cepat daripada npm. - Bun Bundler
bun build: Pembundel (bundler) yang dapat memaketkan file TypeScript, JSX, React, dan CSS untuk browser dan server. - Bun Test Runner
bun test: Pelari uji (test runner) yang kompatibel dengan Jest dan mengutamakan TypeScript. - Dukungan TypeScript & JSX: Bun dapat menjalankan file
.jsx,.ts, dan.tsxsecara langsung tanpa perlu transpilasi tambahan.
perbedaan antara Bun dan Node.js
| Fitur/Aspek | Bun | Node.js |
|---|---|---|
| Mesin JavaScript | JavaScriptCore (dari WebKit/Safari) | V8 (dari Google Chrome) |
| Bahasa Implementasi | Zig | C++ |
| Pendekatan Tooling | All-in-one (runtime, package manager, bundler, test runner built-in) | Runtime saja (membutuhkan alat eksternal seperti npm/yarn/pnpm, Webpack/Rollup, Jest/Mocha) |
| Kecepatan Startup | Umumnya lebih cepat (diklaim 4x lebih cepat dari Node.js) | Relatif lebih lambat |
| Kecepatan Eksekusi | Seringkali menunjukkan performa lebih cepat, terutama pada tugas yang intensif CPU. | Relatif lebih lambat dibandingkan Bun, tetapi sangat matang dan stabil. |
| Maturitas & Ekosistem | Lebih baru, ekosistem masih berkembang. | Sangat matang, ekosistem luas dan stabil digunakan di jutaan aplikasi produksi. |
Install bun
$ curl -fsSL https://bun.com/install | bashMenggunakan bun
Create Project
$ mkdir quickstart
$ cd quickstart
$ bun init
$ bun run index.tsSetup Web Server
const server = Bun.serve({
port: 3000,
fetch(request) {
return new Response("Hello, Bun!");
},
});
console.log(`Server running at http://localhost:${server.port}`);$ bun run index.tsInstall Package
$ bun add figletimport figlet from "figlet";
const server = Bun.serve({
port: 3000,
fetch(request) {
const body = figlet.textSync("Hello, Bun!");
return new Response(body);
},
});
console.log(`Server running at http://localhost:${server.port}`);Development Server
Dengan menggunakan bun --watch, kita bisa mengatur server untuk memantau perubahan pada file dan secara otomatis me-reload server saat ada perubahan.
{
"scripts": {
"start": "bun run index.ts",
"dev": "bun --watch index.ts"
}
}$ bun run devRouting
import figlet from "figlet";
const server = Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/") {
const body = figlet.textSync("Hello, Bun & figlet!");
return new Response(body);
}
if (url.pathname === "/about") {
const body = figlet.textSync("About me!");
return new Response(body);
}
if (url.pathname === "/contact") {
const body = figlet.textSync("Contact us");
return new Response(body);
}
return new Response("404!");
},
});
console.log(`Server running at http://localhost:${server.port}`);silahkan akses:
Throw Error
const server = Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/") {
const body = figlet.textSync("Hello, Bun & figlet!");
return new Response(body);
}
if (url.pathname === "/feed") {
throw new Error("This page is not available yet!");
}
return new Response("404!");
},
});
console.log(`Server running at http://localhost:${server.port}`);
Custom Error Response
const server = Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/") {
const body = figlet.textSync("Hello, Bun & figlet!");
return new Response(body);
}
if (url.pathname === "/feed") {
throw new Error("This page is not available yet!");
}
return new Response("404!");
},
error(error) {
return new Response(`<pre>${error.message}</pre>`, {
headers: {
"Content-Type": "text/html",
},
});
},
});
console.log(`Server running at http://localhost:${server.port}`);- Dengan menambahkan fungsi
error, kita bisa mengatur bagaimana server menangani error yang terjadi. Dalam contoh ini, kita mengembalikan pesan error dalam format HTML.
Read File
const server = Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/greet") {
return new Response(Bun.file("./greet.txt"));
}
return new Response("404!");
},
});
console.log(`Server running at http://localhost:${server.port}`);- Dengan menggunakan
Bun.file, kita bisa membaca file secara langsung dan mengembalikannya sebagai respons. Dalam contoh ini, kita membaca filegreet.txtdan mengembalikannya saat ada permintaan ke/greet.
Framework Rest API (elysia)
Elysia adalah framework untuk membuat REST API (seperti express) dengan Bun yang sangat cepat dan efisien. Elysia menyediakan berbagai fitur seperti routing, middleware, dan dukungan untuk berbagai format data.
$ bun add elysia bun-rest-api
$ cd bun-rest-api
$ bun run devMake Route
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => "Hello Elysia")
.get("/post/:id", ({ params: { id } }) => id)
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Dengan menggunakan Elysia, kita bisa membuat route dengan mudah. Dalam contoh ini, kita membuat route untuk root (
/) dan route dinamis untuk mendapatkan post berdasarkan ID (/post/:id). - params adalah objek yang berisi parameter dari URL. Dalam contoh ini, kita mengambil ID dari URL dan mengembalikannya sebagai respons.
Post Request
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => "Hello Elysia")
.get("/post/:id", ({ params: { id } }) => {
return { id: id, title: "Ini adalah Judul" };
})
.post("/post", (body) => {
return {
message: "Post created successfully",
data: body,
};
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Kita juga bisa membuat route untuk menerima permintaan POST. Dalam contoh ini, kita membuat route
/postyang menerima data dari body permintaan dan mengembalikannya sebagai respons.
Get all routes
Dengan menggunakan wildcard *, kita bisa menangkap semua route yang tidak ditangani sebelumnya.
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => "Hello Elysia")
.get("/post/:id", ({ params: { id } }) => {
return { id: id, title: "Ini adalah Judul" };
})
.post("/post", (body) => {
return {
message: "Post created successfully",
data: body,
};
})
.get("/track/*", () => {
return "Get all routes";
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);Context
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => "Hello Elysia")
.get("/post/:id", ({ params: { id } }) => {
return { id: id, title: "Ini adalah Judul" };
})
.post("/post", (context) => {
return {
message: "Post created successfully",
data: context,
};
})
.get("/track/*", (context) => {
return context;
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);{
"message": "Post created successfully",
"data": {
"request": {},
"store": {},
"set": {
"headers": {},
"status": 200
}
}
}- Context adalah objek yang berisi informasi tentang permintaan dan respons. Dalam contoh ini, kita mengembalikan context pada route
/postdan/track/*. - Context ini bisa digunakan untuk mengakses berbagai informasi seperti headers, query parameters, dan lainnya.
- Return context pada route
/track/*akan mengembalikan informasi lengkap tentang permintaan yang diterima.- Ini termasuk informasi tentang request, store, dan set yang berisi headers dan status.
- Kita bisa menggunakan informasi ini untuk debugging atau logging.
- Dengan menggunakan context, kita tidak dapat mengakses data dari body permintaan secara langsung. Untuk itu, kita perlu menggunakan middleware atau cara lain untuk mengakses body.
set function
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => "Hello Elysia")
.get("/post/:id", ({ params: { id } }) => {
return { id: id, title: "Ini adalah Judul" };
})
.post("/post", ({ body, set }) => {
set.status = 201; // Mengatur status code menjadi 201
return body;
})
.get("/track/*", (context) => {
return context;
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Dengan menggunakan fungsi
set, kita bisa mengatur status code dan headers dari respons. Dalam contoh ini, kita mengatur status code menjadi 403 pada route/post.
Return JSON Response
import { Elysia } from "elysia";
const app = new Elysia()
.get("/tracks", () => {
return new Response(
JSON.stringify({
tracks: [
{ id: 1, title: "Track 1" },
{ id: 2, title: "Track 2" },
{ id: 3, title: "Track 3" },
],
}),
{
headers: {
"Content-Type": "application/json",
},
},
);
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Kita bisa mengembalikan respons dalam format JSON dengan menggunakan
JSON.stringify. Dalam contoh ini, kita mengembalikan daftar track dalam format JSON pada route/tracks.
headers harus diatur secara manual untuk mengembalikan respons dalam format JSON. Jika tidak, respons akan dianggap sebagai teks biasa.
Elysia juga menyediakan cara yang lebih sederhana untuk mengembalikan respons dalam format JSON:
import { Elysia } from "elysia";
const app = new Elysia()
.get("/tracks", () => {
return {
tracks: [
{ id: 1, title: "Track 1" },
{ id: 2, title: "Track 2" },
{ id: 3, title: "Track 3" },
],
};
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);State dan Decorate
Dengan Elysia, kita bisa menyimpan data dalam state dan membuat fungsi dekorasi yang bisa digunakan di seluruh aplikasi.
import { Elysia } from "elysia";
const app = new Elysia()
.get("/", () => "Hello Elysia")
.state("version", "1.0.0")
.decorate("getDate", () => Date.now())
.get("/tracks", ({ store, getDate }) => {
console.log("Store version:", store.version);
console.log("Current date:", getDate());
return {
tracks: [
{ id: 1, title: "Track 1" },
{ id: 2, title: "Track 2" },
{ id: 3, title: "Track 3" },
],
version: store.version,
date: getDate(),
};
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Dengan menggunakan
state, kita bisa menyimpan data yang bisa diakses di seluruh aplikasi. Dalam contoh ini, kita menyimpan versi aplikasi dalam state. - Dengan menggunakan
decorate, kita bisa membuat fungsi yang bisa digunakan di seluruh aplikasi. Dalam contoh ini, kita membuat fungsigetDateyang mengembalikan tanggal saat ini. - Kita bisa mengakses state dan fungsi dekorasi ini di dalam route handler. Dalam contoh ini, kita mengakses versi aplikasi dan tanggal saat ini pada route
/tracks. - Jika ingin lebih banyak meletakkan state, gunakan objek sebagai state:
Store another route in main route
import { Elysia } from "elysia";
// plugin: elysia-plugin
const plugin = new Elysia()
.state("version", "2.0.0")
.decorate("getNewDate", () => Date.now() + 1000)
.get("/elysia-plugin", () => "Hi");
const app = new Elysia()
.get("/", () => "Hello Elysia")
.use(plugin) // Use the plugin
.get("/tracks", ({ store, getDate, getNewDate }) => {
console.log("Store version:", store.version);
console.log("Current date:", getDate());
console.log("New date:", getNewDate());
return {
tracks: [
{ id: 1, title: "Track 1" },
{ id: 2, title: "Track 2" },
{ id: 3, title: "Track 3" },
],
version: store.version,
date: getDate(),
newDate: getNewDate(),
};
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Jika kita menambahkan route dari luar main route, kita bisa menggunakan
useuntuk menggunakannya sebagai plugin. Dalam contoh ini, kita membuat plugin yang berisi state dan fungsi dekorasi, lalu menggunakannya di main route. - Dengan cara ini, kita bisa memisahkan kode menjadi beberapa file dan menggunakannya sebagai plugin yang bisa digunakan di seluruh aplikasi.
- Berikut adalah contoh respons JSON yang dihasilkan oleh route
/tracks:
{
"tracks": [
{
"id": 1,
"title": "Track 1"
},
{
"id": 2,
"title": "Track 2"
},
{
"id": 3,
"title": "Track 3"
}
],
"version": "3.0.0", // versi dari state
"date": 1752601981161,
"newDate": 1752601982161
}Import plugin dari file lain:
import Elysia from "elysia";
export const plugin = new Elysia()
.state("version", "3.0.0")
.decorate("getNewDate", () => Date.now() + 1000)
.get("/elysia-plugin", () => "Hi");import { Elysia } from "elysia";
import { plugin } from "./plugin";
const app = new Elysia()
.get("/", () => "Hello Elysia")
.use(plugin) // Use the plugin
.get("/tracks", ({ store, getDate, getNewDate }) => {
console.log("Store version:", store.version);
console.log("Current date:", getDate());
console.log("New date:", getNewDate());
return {
tracks: [
{ id: 1, title: "Track 1" },
{ id: 2, title: "Track 2" },
{ id: 3, title: "Track 3" },
],
version: store.version,
date: getDate(),
newDate: getNewDate(),
};
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);Grouping Routes
import { Elysia } from "elysia";
const app = new Elysia();
app.group("/v1", (app) =>
app
.get("/", () => "Hello Elysia v1")
.get("/users", () => "List of users in v1")
.get("/user/:id", ({ params: { id } }) => id),
);
app.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Dengan menggunakan
group, kita bisa mengelompokkan route berdasarkan versi atau kategori tertentu. Dalam contoh ini, kita mengelompokkan route dengan prefix/v1. - Untuk mengakses route yang dikelompokkan, kita bisa menggunakan URL seperti
http://localhost:3000/v1/usersatauhttp://localhost:3000/v1/user/1.
Another example:
import { Elysia } from "elysia";
const app = new Elysia();
app.group("/v1", (app) =>
app
.get("/", () => "Hello Elysia v1")
.get("/users", () => "List of users in v1")
.get("/user/:id", ({ params: { id } }) => id)
.group("/products", (app) =>
app
.get("/", () => "List of products")
.get("/:id", ({ params: { id } }) => `Product ID: ${id}`)
.post(
"/",
({ body }: { body: any }) =>
`Created product with name: ${body.name}`,
)
.put(
"/:id",
({
params: { id },
body,
}: {
params: { id: string };
body: { name: string };
}) => `Updated product ${id} with name: ${body.name}`,
)
.delete(
"/:id",
({ params: { id } }) => `Deleted product with ID: ${id}`,
),
),
);
app.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Dalam contoh ini, kita mengelompokkan route produk di dalam grup
/v1. Kita bisa membuat route untuk mendapatkan daftar produk, mendapatkan produk berdasarkan ID, membuat produk baru, memperbarui produk, dan menghapus produk. - Dengan cara ini, kita bisa mengorganisir route dengan lebih baik dan membuatnya lebih mudah untuk dikelola.
- unutk put, delete kita bisa menggunakan
paramsuntuk mendapatkan ID dari URL danbodyuntuk mendapatkan data yang dikirimkan dalam permintaan.
Body Validation
import { Elysia, t } from "elysia";
const app = new Elysia();
app.post(
"/v1/user",
({ body }) => {
return body;
},
{
body: t.Object({
name: t.String(),
age: t.Number(),
}),
response: {
name: t.String(),
age: t.String(),
},
},
);
app.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Dengan menggunakan
t.Object, kita bisa melakukan validasi pada body permintaan. Dalam contoh ini, kita memvalidasi bahwa body harus berupa objek dengan propertiname(string) danage(number). - jika body tidak sesuai dengan skema yang ditentukan, Elysia akan mengembalikan respons error secara otomatis.
- Dan juga bisa mengatur skema respons yang diharapkan dengan menggunakan
responsepada opsi route. Dalam contoh ini, kita mengatur bahwa respons harus berupa objek dengan propertiname(string) danage(string).
import { t } from "elysia";
export const signinDTO = t.Object({
name: t.String(),
age: t.Number(),
});import { Elysia } from "elysia";
import { signinDTO } from "./models";
const app = new Elysia();
app.post(
"/v1/user",
({ body }) => {
return body;
},
{
body: signinDTO,
response: signinDTO,
},
);
app.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Kita bisa memisahkan skema validasi ke dalam file terpisah untuk menjaga kebersihan kode. Dalam contoh ini, kita membuat file
models.tsyang berisi skema validasi untuk permintaan sign-in. [[#Create Project]]
Params Validation
import { Elysia, t } from "elysia";
import { signinDTO } from "./models";
const app = new Elysia();
app
.post(
"/v1/user",
({ body }) => {
return body;
},
{
body: signinDTO,
response: signinDTO,
},
)
.get(
"/v1/user/:id",
({ params: { id } }) => {
return id;
},
{
params: t.Object({
id: t.Numeric(),
}),
},
);
app.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);- Kita juga bisa melakukan validasi pada parameter URL dengan menggunakan
t.Object. Dalam contoh ini, kita memvalidasi bahwa parameteridharus berupa angka pada route/v1/user/:id. - Jika parameter tidak sesuai dengan skema yang ditentukan, Elysia akan mengembalikan respons error secara otomatis.
Frontend Project
$ bun create vite react-app
$ cd react-app
$ bun install
$ bun run dev "scripts": {
"dev": "bunx --bun vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},- Dengan menggunakan
bun create vite, kita bisa membuat proyek frontend dengan Vite dan React. Setelah itu, kita bisa menginstal dependensi denganbun installdan menjalankan server pengembangan denganbun run dev. - Ubah
package.jsonuntuk menggunakanbunxagar bisa menjalankan bun dengan benar di dalam proyek Vite.