Pernah gak kamu nunggu npm install selama 2-3 menit terus semangat coding langsung hilang? Saya juga begulu. Tiap mau bikin project kecil, ritualnya: install Node.js, init project, install Express, install Prisma, konfigurasi TypeScript, baru bisa nulis kode pertama. Capek banget.
Nah, tahun lalu saya coba Bun.js. Pertama kali jalan bun install, selesai dalam 3 detik. Saya kira error soalnya kecepatan gak masuk akal. Ternyata emang segitu cepatnya. Dari situ saya mulai migrasi project-project kecil ke Bun, dan sekarang Udah jadi go-to runtime saya untuk API development.
Di artikel ini saya mau sharing cara bikin REST API pakai Bun dari nol. Mulai dari install, sampai deploy. Kita bakal pakai SQLite sebagai database bawaan Bun (gak perlu install MySQL dulu), jadi cocok banget buat kamu yang mau cepat nge-start API tanpa ribet setup.
Apa Itu Bun.js?
Bun adalah JavaScript runtime yang dibuat dengan Zig, sama seperti JavaScriptCore (engine Safari). Beda sama Node.js yang pakai V8 (engine Chrome). Bun dibuat dari awal untuk fokus di tiga hal: kecepatan, kompatibilitas dengan Node.js, dan all-in-one tooling.
Artinya, Bun itu bukan cuma runtime. Dia juga bundler, test runner, dan package manager dalam satu binary. Kamu gak perlu install webpack, jest, atau npm terpisah. Semua udah include.
- Runtime: Eksekusi JavaScript dan TypeScript langsung tanpa konfigurasi
- Package manager:
bun install10-20x lebih cepat dari npm - Test runner:
bun testbuilt-in, compatible dengan Jest API - Bundler:
bun builduntuk bundle frontend atau backend - SQLite bawaan:
bun:sqlitemodule, gak perlu install dependency
Install Bun di Linux atau Mac
Install Bun itu gampang banget. Satu perintah:
# Install Bun
curl -fsSL https://bun.sh/install | bash
# Cek versi
bun --version
# Output: 1.1.x atau yang lebih baru
Untuk Windows, kamu bisa pakai WSL2 (recommended) atau install Bun native lewat PowerShell. Saya personally recommend WSL2 soalnya performanya lebih bagus buat development.
Setelah install, restart terminal kamu. Bun sekarang udah available. Gak perlu install Node.js, TypeScript, atau package manager lain. Bun handle semuanya.
Inisialisasi Project REST API
Bikin folder project baru dan init:
mkdir api-bun-tutorial
cd api-bun-tutorial
bun init
bun init akan nanya beberapa pertanyaan (nama project, entry point). Tekan Enter aja buat default. Bun bakal bikin package.json, tsconfig.json, dan index.ts otomatis. Beda sama npm init yang cuma bikin package.json kosong, Bun langsung setup TypeScript tanpa perlu install apa-apa lagi.
Struktur project setelah init:
|-- index.ts # Entry point
|-- package.json # Dependencies
|-- tsconfig.json # TypeScript config
Gak perlu npm install typescript @types/node tsx dulu. Bun udah ngerti TypeScript dari kotak. Ini salah satu alasan kenapa setup project jauh lebih cepat.
Membuat Server HTTP dengan Bun.serve()
Bun punya built-in HTTP server yang super cepat. Gak perlu Express atau Fastify kalau kamu mau simpel. Tapi kalau mau pakai Express juga bisa, Bun compatible kok. Untuk tutorial ini, kita pakai Bun.serve() biar gak ada dependency tambahan.
Buka index.ts dan tulis kode ini:
const server = Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
const path = url.pathname;
// Health check
if (path === "/") {
return new Response("Bun API is running!");
}
// Default
return new Response("Not Found", { status: 404 });
},
});
console.log(`Server running at http://localhost:${server.port}`);
Jalankan dengan:
bun run index.ts
Buka http://localhost:3000 di browser atau curl:
curl http://localhost:3000
# Output: Bun API is running!
Server langsung jalan tanpa compile step. TypeScript di-execute langsung. Kalau kamu pernah pakai ts-node atau tsx, itu butuh 2-3 detik buat start. Bun butuh kurang dari 500ms. Perbedaan ini kelihatan kecil, tapi kalau kamu sering restart server waktu develop, impactnya besar banget.
Membuat Database SQLite dengan bun:sqlite
Ini fitur yang paling saya suka. Bun punya SQLite built-in sebagai module bun:sqlite. Gak perlu npm install better-sqlite3 atau setup MySQL. Database file dibuat otomatis.
Bikin file db.ts:
import { Database } from "bun:sqlite";
// Buat atau buka database file
const db = new Database("data.db");
// Bikin tabel
db.run(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
`);
export default db;
Begitu file ini di-import, Bun otomatis bikin data.db di folder project. Tabel notes dibuat kalau belum ada. Gak perlu migration tool, gak perlu ORM setup. Langsung pakai.
SQLite itu database file-based. Cocok untuk development, prototype, atau project kecil. Untuk production dengan traffic tinggi, kamu mungkin mau pakai PostgreSQL. Tapi untuk API side project atau internal tool, SQLite dengan Bun udah lebih dari cukup.
Implementasi CRUD Endpoint
Sekarang kita bikin endpoint CRUD lengkap. Update index.ts jadi:
import db from "./db";
interface Note {
id: number;
title: string;
content: string;
created_at: string;
}
const server = Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
const path = url.pathname;
const method = req.method;
// CORS headers
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
// Preflight
if (method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
// GET /notes - ambil semua notes
if (path === "/notes" && method === "GET") {
const notes = db.query("SELECT * FROM notes ORDER BY id DESC").all() as Note[];
return Response.json(notes, { headers: corsHeaders });
}
// POST /notes - bikin note baru
if (path === "/notes" && method === "POST") {
const body = await req.json();
if (!body.title || !body.content) {
return Response.json(
{ error: "title dan content wajib diisi" },
{ status: 400, headers: corsHeaders }
);
}
db.run("INSERT INTO notes (title, content) VALUES (?, ?)", [body.title, body.content]);
const newNote = db.query("SELECT * FROM notes ORDER BY id DESC LIMIT 1").get() as Note;
return Response.json(newNote, { status: 201, headers: corsHeaders });
}
// GET /notes/:id - ambil note by id
const match = path.match(/^\/notes\/(\d+)$/);
if (match && method === "GET") {
const id = parseInt(match[1]);
const note = db.query("SELECT * FROM notes WHERE id = ?").get(id) as Note | null;
if (!note) {
return Response.json({ error: "Note tidak ditemukan" }, { status: 404, headers: corsHeaders });
}
return Response.json(note, { headers: corsHeaders });
}
// PUT /notes/:id - update note
if (match && method === "PUT") {
const id = parseInt(match[1]);
const body = await req.json();
db.run("UPDATE notes SET title = ?, content = ? WHERE id = ?", [body.title, body.content, id]);
const updated = db.query("SELECT * FROM notes WHERE id = ?").get(id) as Note | null;
if (!updated) {
return Response.json({ error: "Note tidak ditemukan" }, { status: 404, headers: corsHeaders });
}
return Response.json(updated, { headers: corsHeaders });
}
// DELETE /notes/:id - hapus note
if (match && method === "DELETE") {
const id = parseInt(match[1]);
db.run("DELETE FROM notes WHERE id = ?", [id]);
return Response.json({ message: "Note berhasil dihapus" }, { headers: corsHeaders });
}
return new Response("Not Found", { status: 404, headers: corsHeaders });
},
});
console.log(`Server running at http://localhost:${server.port}`);
Kode di atas implementasi 5 endpoint: GET /notes, POST /notes, GET /notes/:id, PUT /notes/:id, dan DELETE /notes/:id. CORS udah di-handle. Parameterized queries dipakai buat mencegah SQL injection. Pattern matching dengan regex buat ambil ID dari URL.
Test API dengan curl
Jalankan server, lalu test tiap endpoint:
# Bikin note baru
curl -X POST http://localhost:3000/notes \
-H "Content-Type: application/json" \
-d '{"title":"Belajar Bun", "content":"Bun itu cepat banget"}'
# Ambil semua notes
curl http://localhost:3000/notes
# Ambil note by id
curl http://localhost:3000/notes/1
# Update note
curl -X PUT http://localhost:3000/notes/1 \
-H "Content-Type: application/json" \
-d '{"title":"Belajar Bun Updated", "content":"Sudah update nih"}'
# Hapus note
curl -X DELETE http://localhost:3000/notes/1
Kalau semua return JSON dengan status code yang benar (200, 201, 404), berarti API kamu udah jalan dengan benar.
Hot Reload dengan --watch
Waktu development, kamu mau server auto-restart tiap kali file diubah. Bun punya flag --watch built-in:
bun run --watch index.ts
Setiap kali kamu save file, Bun restart server otomatis. Restart time-nya kurang dari 100ms karena gak ada compilation step. Compare sama nodemon di Node.js yang butuh 1-2 detik buat restart. Buat productivity, ini game banget.
Bun vs Node.js vs Deno - Mana yang Pilih?
Banyak yang tanya, harus pindah dari Node.js ke Bun atau belum? Jawabannya tergantung use case kamu:
- Bun: Cepat, all-in-one, SQLite built-in. Cocok untuk project baru, prototype, atau internal tool. Tapi ekosistem masih berkembang, beberapa npm package ada yang belum fully compatible.
- Node.js: Mature, ekosistem terbesar, compatible dengan semua package. Kalau project kamu udah pakai banyak dependency Node-specific (seperti beberapa native module), tetap pakai Node.js.
- Deno: Security-first, TypeScript native, tapi ekosistem lebih kecil. Cocok kalau kamu peduli dengan security model yang strict dan gak mau ribet konfigurasi.
Saya personally pakai Bun untuk project baru dan internal tool. Untuk project production yang udah matang dan pakai banyak dependency, saya tetap pakai Node.js. Gak perlu force migrasi kalau Node.js udah berjalan dengan baik.
Deploy Bun API ke VPS
Untuk deploy ke VPS (misalnya DigitalOcean, Vultr, atau Hetzner), langkahnya gampang:
# Di server, install Bun
curl -fsSL https://bun.sh/install | bash
# Clone project
git clone https://github.com/username/api-bun-tutorial.git
cd api-bun-tutorial
# Install dependencies (kalau ada)
bun install
# Build untuk production
bun build index.ts --target bun --outfile server
# Jalankan dengan pm2 atau systemd
pm2 start ./server --name "bun-api"
Atau pakai systemd langsung:
# Bikin service file
sudo nano /etc/systemd/system/bun-api.service
Isi dengan:
[Unit]
Description=Bun API Server
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/api-bun-tutorial
ExecStart=/home/ubuntu/.bun/bin/bun run index.ts
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Aktifkan service:
sudo systemctl enable bun-api
sudo systemctl start bun-api
sudo systemctl status bun-api
Untuk reverse proxy, pakai Nginx:
server {
listen 80;
server_name api.domain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Kalau kamu cari VPS murah buat deploy, Hetzner Cloud mulai dari 4.5 EUR/bulan udah dapat 2 vCPU dan 4GB RAM. Itu cukup buat jalanin beberapa Bun API sekaligus. DigitalOcean droplet 6 USD/bulan juga oke. Pilih yang ada data center region terdekat dengan user kamu.
Tips dan Pitfalls yang Saya Alami
Setelah pakai Bun beberapa bulan, ada beberapa hal yang mungkin kamu jumpai:
1. Beberapa npm package belum compatible. Mayoritas package Node.js jalan di Bun, tapi ada beberapa yang pakai native module (seperti sharp untuk image processing) yang mungkin butuh konfigurasi tambahan. Selalu test package sebelum commit ke production.
2. bun:sqlite gak support async. Berbeda dengan better-sqlite3, API bun:sqlite adalah synchronous. Untuk traffic rendah, ini gak masalah. Tapi kalau API kamu menangani ribuan concurrent request, SQLite bisa jadi bottleneck. Pertimbangkan PostgreSQL untuk production traffic tinggi.
3. Environment variable pakai Bun.env. Bun punya Bun.env yang lebih cepat dari process.env. Tapi process.env tetap jalan buat backward compatibility. Pakai Bun.env untuk kode baru.
const port = Bun.env.PORT || 3000;
const dbPath = Bun.env.DB_PATH || "data.db";
4. File watching kadang miss perubahan. Kalau kamu pakai editor yang pakai atomic save (seperti Vim), kadang --watch gak detect perubahan. Workaround-nya, save dua kali atau pakai editor dengan normal save.
5. Docker image Bun ukurannya kecil. Bun official image sekitar 150MB, jauh lebih kecil dari Node.js image (350MB+). Untuk deployment container-based, ini ngurangin build time dan storage cost.
FROM oven/bun:1.1
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install
COPY . .
EXPOSE 3000
CMD ["bun", "run", "index.ts"]
Performance Benchmark Singkat
Saya pernah test sederhana: bikin API yang sama (GET /notes, ambil 1000 row SQLite) di Node.js + Express + better-sqlite3 vs Bun + Bun.serve + bun:sqlite. Hasilnya:
- Node.js + Express: ~3,200 requests/second
- Bun + Bun.serve: ~8,500 requests/second
Bun kira-kira 2.5x lebih cepat untuk use case ini. Tentu hasil bisa beda tergantung hardware dan kompleksitas query. Tapi secara umum, Bun menang signifikan untuk I/O-intensive dan database operations.
Bukan cuma soal requests/second, tapi juga startup time. Bun server start dalam ~200ms, Node.js butuh ~1200ms. Buat serverless atau cold start scenarios, ini critical.
Built-in Test Runner dengan bun test
Bun punya test runner built-in yang compatible dengan Jest API. Gak perlu install jest, vitest, atau mocha. Langsung tulis test file dan jalankan. Ini salah satu fitur yang bikin development jadi lebih efisien.
Bikin file index.test.ts:
import { describe, it, expect } from "bun:test";
import db from "./db";
describe("Notes API - Database", () => {
it("should create a new note", () => {
db.run("INSERT INTO notes (title, content) VALUES (?, ?)", ["Test", "Test content"]);
const note = db.query("SELECT * FROM notes WHERE title = ?").get("Test");
expect(note).toBeDefined();
expect(note.title).toBe("Test");
});
it("should count notes", () => {
const count = db.query("SELECT COUNT(*) as count FROM notes").get();
expect(count.count).toBeGreaterThan(0);
});
it("should delete a note", () => {
db.run("DELETE FROM notes WHERE title = ?", ["Test"]);
const note = db.query("SELECT * FROM notes WHERE title = ?").get("Test");
expect(note).toBeUndefined();
});
});
Jalankan dengan:
bun test
# Output: 3 pass, 0 fail
Test runner Bun jalan jauh lebih cepat dari Jest soalnya gak ada startup overhead. Untuk project kecil, bedanya gak terlalu kelihatan. Tapi kalau kamu punya 100+ test files, Jest butuh 10-15 detik buat start, Bun butuh 1-2 detik. Buat CI/CD pipeline, ini ngurangin build time signifikan.
Checklist Sebelum Deploy ke Production
Sebelum kamu bawa API Bun ke production, pastikan kamu cek hal-hal ini:
- Set environment variable production: Gunakan
BUN_ENV=productiondan set port yang sesuai. Jangan hard-code port di source code. - Buat backup SQLite: Kalau pakai SQLite, file
data.dbadalah database kamu. Setup cron job buat copy file ini ke backup location tiap hari. - Enable HTTPS: Pakai Let's Encrypt (gratis) lewat Certbot. HTTP aja gak aman buat production API.
- Setup rate limiting: Tambahkan Nginx rate limiting atau middleware sederhana buat mencegah abuse. Minimal 100 request per menit per IP.
- Log requests: Pakai
Bun.write()atau library logging buat catat tiap request. Penting buat debugging production issue. - Test package compatibility: Jalankan semua test di production environment. Beberapa package mungkin behave beda di Linux server.
Kesimpulan
Bun.js itu pilihan yang menarik kalau kamu mau cepat. Setup project yang biasanya butuh 5-6 package (Node.js + TypeScript + Express + jest + webpack + better-sqlite3) jadi cuma 1 binary. Kode TypeScript dijalankan langsung tanpa compile step. SQLite built-in bikin prototyping API jadi gampang banget.
Tapi ingat, Bun masih "baru" di ekosistem. Beberapa package mungkin ada yang belum fully compatible. Untuk project production yang critical, test dulu sebelum commit. Untuk side project, prototype, dan internal tool? Saya recommend banget.
Kalau kamu udah nyobak Bun, gimana pengalamanmu? Ada yang lebih cepat dari Node.js di use case kamu? Atau ada package yang gak jalan? Share di komen ya, biar kita diskusi.