So sánh hướng tiếp cận: Node.js + Express vs. Bun + ElysiaJS
Trong hơn một thập kỷ qua, việc khởi tạo một backend JavaScript thường gắn liền với Node.js kết hợp cùng Express hoặc Fastify. Tech stack này hiện vẫn đang vận hành vô số hệ thống production. Tuy nhiên, các dự án hiện đại thường biến thành một mớ ghép nối phức tạp gồm nhiều công cụ rời rạc để transpile TypeScript (tsx/esbuild), load biến môi trường (dotenv), chạy test (jest/vitest) và validate schema (zod).
Bun gom tất cả các công cụ riêng lẻ này vào một binary native duy nhất vận hành trên nền JavaScriptCore. Khi kết hợp Bun với ElysiaJS—một web framework được thiết kế tối ưu riêng cho các HTTP primitive của Bun—bạn sẽ đạt được thời gian cold start dưới 15ms, thực thi TypeScript trực tiếp và static type inference mượt mà. Dù trong các bài benchmark giả lập hay tải I/O thực tế, Elysia đều xử lý ổn định trên 150.000 requests mỗi giây với mức tiêu thụ RAM cơ bản dưới 60MB, so với mức chỉ khoảng 20.000 req/s của Express.
Ưu và nhược điểm khi chuyển sang Bun và ElysiaJS
Ưu điểm
- Thông lượng cao & Độ trễ thấp: Bun xử lý HTTP thông qua các C++ binding cấp thấp, mang lại số lượng request mỗi giây cao hơn từ 4 đến 8 lần so với Express.
- TypeScript không cần cấu hình: Chạy trực tiếp các file
.tsvà.tsxmà không cần vật lộn với path mapping trongtsconfig.jsonhay các bước build phức tạp. - Validation TypeBox tích hợp sẵn: Quá trình compile schema chạy qua JIT compilation của TypeBox, giúp validate payload nhanh hơn tới 100 lần so với các thư viện runtime thông thường như Joi.
- Type Safety toàn diện (End-to-End) với Eden Treaty: Export type definition từ server và gọi các API endpoint có đầy đủ kiểu dữ liệu ở phía frontend, hỗ trợ trọn vẹn tính năng autocomplete cùng khả năng kiểm tra lỗi ngay khi compile.
Nhược điểm
- Tính tương thích ở các trường hợp đặc thù với Node.js: Dù Bun hỗ trợ hầu hết các package npm, những thư viện cũ phụ thuộc vào các hàm nội bộ không công khai của V8 hoặc C++ native addon đời cũ vẫn có thể phát sinh lỗi.
- Hệ sinh thái Middleware: Hệ sinh thái plugin của Elysia đã bao phủ auth, OpenAPI, CORS và rate limiting, nhưng số lượng plugin bên thứ ba cho các nhu cầu ngách vẫn còn khiêm tốn hơn so với kho thư viện hơn chục năm tuổi của Express.
Cài đặt khuyến nghị và điều kiện tiên quyết
Bạn chỉ cần cài đặt Bun trên máy là có thể bắt đầu theo dõi hướng dẫn. Bun cung cấp binary chính thức cho macOS, Linux và Windows Subsystem for Linux (WSL).
# Cài đặt Bun trên Linux / macOS
curl -fsSL https://bun.sh/install | bash
# Kiểm tra phiên bản cài đặt (khuyến nghị phiên bản 1.1 trở lên)
bun --version
Về trình soạn thảo mã nguồn, Visual Studio Code kết hợp với các extension chính thức của Elysia và Tailwind/TypeScript sẽ mang lại trải nghiệm tự động gợi ý lệnh (auto-completion) và kiểm tra lỗi schema trực quan ngay trong code.
Hướng dẫn triển khai từng bước
Bước 1: Khởi tạo dự án
Tạo một thư mục dự án mới và cài đặt các dependency cốt lõi. Bun cài đặt package chỉ trong khoảng 50 đến 200 mili-giây nhờ cơ chế hard link toàn cục:
mkdir bun-elysia-api
cd bun-elysia-api
bun init -y
bun add elysia @elysiajs/cors @elysiajs/swagger
Bước 2: Xây dựng server cốt lõi và các REST endpoint
Tạo file index.ts. Chúng ta sẽ định nghĩa một danh mục sản phẩm lưu trong bộ nhớ (in-memory) cùng các route GET và POST có định kiểu rõ ràng, đồng thời sử dụng schema builder của Elysia (t) để validate payload tức thì.
import { Elysia, t } from 'elysia';
import { swagger } from '@elysiajs/swagger';
import { cors } from '@elysiajs/cors';
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
}
const products: Product[] = [
{ id: '1', name: 'Bàn phím cơ', price: 120, inStock: true },
{ id: '2', name: 'Chuột công thái học', price: 65, inStock: false },
];
export const app = new Elysia()
.use(cors())
.use(swagger({
documentation: {
info: {
title: 'API Danh mục sản phẩm',
version: '1.0.0',
},
},
}))
.get('/api/products', () => products)
.get(
'/api/products/:id',
({ params, error }) => {
const product = products.find((item) => item.id === params.id);
if (!product) {
return error(404, { message: 'Không tìm thấy sản phẩm' });
}
return product;
},
{
params: t.Object({
id: t.String(),
}),
}
)
.post(
'/api/products',
({ body, set }) => {
const newProduct: Product = {
id: (products.length + 1).toString(),
...body,
};
products.push(newProduct);
set.status = 201;
return newProduct;
},
{
body: t.Object({
name: t.String({ minLength: 2 }),
price: t.Number({ minimum: 0 }),
inStock: t.Boolean(),
}),
}
)
.listen(3000);
console.log(`Server đang chạy tại http://${app.server?.hostname}:${app.server?.port}`);
export type App = typeof app;
Khởi động server với chế độ hot reload được kích hoạt:
bun run --watch index.ts
Mở http://localhost:3000/swagger trên trình duyệt. Elysia sẽ tự động chuyển đổi các ràng buộc schema TypeBox thành giao diện Swagger UI trực quan với đầy đủ tài liệu về request và response.
Bước 3: Kết nối Frontend bằng Eden Treaty
Eden Treaty đồng bộ hóa các định nghĩa route từ backend với code frontend mà không cần đến code generator, build script hay thao tác đồng bộ OpenAPI thủ công. Trước tiên, hãy cài đặt Eden trong workspace client của bạn:
bun add @elysiajs/eden
Import kiểu dữ liệu App vào script client và khởi tạo Treaty client:
import { treaty } from '@elysiajs/eden';
import type { App } from './index';
const client = treaty<App>('http://localhost:3000');
async function fetchAndCreateProducts() {
// 1. Gọi GET có đầy đủ type và autocomplete cho đường dẫn URL
const { data: allProducts, error: listError } = await client.api.products.get();
if (listError) {
console.error('Không thể tải danh sách sản phẩm:', listError.value);
return;
}
console.log(`Đã tải ${allProducts.length} sản phẩm:`, allProducts);
// 2. Gọi POST được kiểm tra type nghiêm ngặt: TypeScript sẽ báo lỗi ngay nếu sai kiểu dữ liệu
const { data: created, error: createError } = await client.api.products.post({
name: 'Hub chuyển đổi USB-C',
price: 89,
inStock: true,
});
if (createError) {
console.error('Xác thực dữ liệu thất bại:', createError.value);
return;
}
console.log(`Đã tạo sản phẩm có ID: ${created.id}`);
}
fetchAndCreateProducts();
Bước 4: Triển khai Production với Docker
Container hóa các ứng dụng Bun sẽ tạo ra các deployment artifact vô cùng gọn nhẹ. Một Docker image Bun nền tảng Alpine thông thường có dung lượng dưới 100MB và khởi động chỉ mất khoảng 20 mili-giây.
Tạo một file Dockerfile tại thư mục gốc của dự án:
# Multi-stage production build
FROM oven/bun:1-alpine AS base
WORKDIR /app
# Cache và cài đặt dependencies
COPY package.json bun.lock* bun.lockb* ./
RUN bun install --frozen-lockfile --production
# Sao chép mã nguồn
COPY . .
# Chạy container với user non-root để đảm bảo bảo mật
USER bun
EXPOSE 3000
ENV NODE_ENV=production
CMD ["bun", "run", "index.ts"]
Build container image và chạy thử nghiệm tại local:
docker build -t bun-elysia-api .
docker run -p 3000:3000 bun-elysia-api
Giờ đây bạn đã có một REST API type-safe toàn diện (end-to-end), khởi động tức thì, kiểm tra schema nghiêm ngặt với tốc độ vượt trội và hoàn toàn không cần code boilerplate ở phía client.

