Frontend Engineer's Guide to Backend with Node.js, Bun & Deno

Bạn đã viết React hoặc Angular mấy năm trời. Component, state management, routing, build tool — rành hết. Nhưng mỗi lần ai nhắc đến "backend" là lại thấy như bước vào thế giới khác — server, database, middleware, deployment pipeline.
Sự thật là: bạn đã biết nhiều hơn mình tưởng. JavaScript vẫn là JavaScript. TypeScript vẫn là TypeScript. Những tư duy bạn xây dựng suốt mấy năm làm frontend chuyển thẳng sang backend luôn. Bạn không cần học ngôn ngữ mới — bạn chỉ cần một ngữ cảnh mới.
Bài viết này cho bạn lộ trình thực tế, không lý thuyết suông, để đi từ frontend engineer thành người tự tin xây dựng, deploy và bảo trì backend service.
Bạn Sẽ Học Được Gì
Kết quả học tập:
✅ Những kỹ năng frontend nào chuyển thẳng sang backend
✅ Các khái niệm cần học (và cái nào bỏ qua được)
✅ Cách chọn giữa Node.js, Bun và Deno
✅ Lộ trình từng bước từ "hello world" đến production API
✅ Những lỗi phổ biến frontend engineer hay mắc khi làm backend
✅ Cách xây dựng REST API đầu tiên từ đầu
Phần 1: Những Gì Bạn Đã Biết (Nhiều Hơn Bạn Nghĩ)
Trước khi học cái mới, hãy kiểm kê lại đã. Là frontend engineer, bạn đã có sẵn:
JavaScript / TypeScript — Vũ Khí Chính
Đây là tài sản lớn nhất. Ngôn ngữ y chang. Kiến thức của bạn về:
- Biến, hàm, class, module
- Promise, async/await, xử lý lỗi
- Các method array (
.map(),.filter(),.reduce()) - Destructuring, spread operator, template literal
- TypeScript type, interface, generic
Tất cả chuyển 1:1 sang backend. Bạn không bắt đầu từ số 0.
Quản Lý Package
Bạn đã biết npm (hoặc yarn, pnpm). Bạn đã xử lý package.json, lockfile, xung đột dependency, và semantic versioning. Project Node.js backend dùng y hệt bộ công cụ này.
Lập Trình Bất Đồng Bộ
Đây là chỗ phần lớn người mới học backend chật vật — nhưng bạn đã làm mấy năm rồi. Mỗi lần gọi fetch(), mỗi lần cập nhật state sau khi nhận response từ API, mỗi useEffect với cleanup function — đó chính là lập trình bất đồng bộ. Trên backend, cùng pattern đó áp dụng cho database query, đọc file, và HTTP request.
JSON Mọi Nơi
Bạn đã tư duy bằng JSON. Request body, response payload, file cấu hình — format dữ liệu không thay đổi.
Build Tool và Quy Trình Dev
Webpack, Vite, esbuild, ESLint, Prettier, CI/CD pipeline — bạn đã config hết rồi. Project backend dùng tooling tương tự (thường còn đơn giản hơn).
Phần 2: Thực Sự Cần Học Gì Mới
Giờ hãy thành thật về những thứ bạn chưa biết. Đây là những khái niệm thật sự mới khi chuyển sang backend.
1. HTTP — Từ Phía Bên Kia
Ở frontend, bạn gửi HTTP request. Ở backend, bạn nhận chúng.
Bạn luôn là client. Giờ bạn là server. Nghĩa là cần hiểu:
- Cách parse request đến (body, header, query param, URL param)
- Cách gửi response (status code, header, JSON body)
- Routing hoạt động thế nào trên server (không phải React Router — mà là mapping URL → handler thực sự)
| Góc Nhìn Frontend | Góc Nhìn Backend |
|---|---|
fetch('/api/users') | Nhận request tại /api/users |
Đọc response.json() | Viết res.json({ users }) |
| Xử lý loading/error state | Xử lý validation/auth/error |
| Một user tại một thời điểm | Hàng nghìn request đồng thời |
2. Database — Vùng Đất Mới
Đây là khoảng trống kiến thức lớn nhất với hầu hết frontend engineer. Ở frontend, data đến từ API. Ở backend, bạn là người lấy data từ database.
Bạn cần học:
- SQL cơ bản:
SELECT,INSERT,UPDATE,DELETE,JOIN - ORM: Prisma, Drizzle, hoặc TypeORM (biến SQL thành TypeScript)
- Thiết kế database: Bảng, quan hệ, index, migration
- Quản lý kết nối: Connection pool, connection string
Tin vui: Nếu bạn đã dùng Prisma với Next.js, bạn đã có lợi thế rồi.
3. Authentication & Authorization
Ở frontend, bạn lưu token và gửi kèm request. Ở backend, bạn:
- Xác thực token đó
- Hash mật khẩu (không bao giờ lưu plain text)
- Quản lý session hoặc JWT
- Quyết định ai được truy cập gì (role, permission)
4. Truy Cập File System
Code frontend chạy trong sandbox — không truy cập file được. Code backend có thể đọc ghi file, tạo thư mục, stream file lớn, và theo dõi thay đổi file.
// Cái này bất khả thi trên browser, bình thường trên backend
import { readFile, writeFile } from 'node:fs/promises';
const data = await readFile('./config.json', 'utf-8');
const config = JSON.parse(data);
config.lastUpdated = new Date().toISOString();
await writeFile('./config.json', JSON.stringify(config, null, 2));5. Environment & Bảo Mật
Ở frontend, mọi thứ đều public — user có thể xem source code. Ở backend:
- Secret giữ được bí mật (API key, mật khẩu database)
- Biến môi trường (file
.env) chứa cấu hình - Validate input là bắt buộc (đừng bao giờ tin user input)
- Bạn xử lý dữ liệu nhạy cảm (thông tin cá nhân, thanh toán)
Phần 3: Chọn Runtime Nào
Bạn có 3 lựa chọn chính để chạy JavaScript trên server. Cả 3 chạy cùng ngôn ngữ, nhưng khác nhau về triết lý và tính năng.
Node.js — Chuẩn Mực Lâu Năm
- Tạo bởi: Ryan Dahl, 2009
- Engine: V8 (giống Chrome)
- Package manager: npm (registry lớn nhất thế giới)
- Ecosystem: Khổng lồ — Express, Fastify, NestJS, hàng triệu package
- Khi nào chọn: Làm production, nhu cầu thị trường việc làm, support thư viện tối đa
# Cài đặt và chạy
node --version
node server.js
# Hoặc với TypeScript
npx tsx server.tsBun — Tân Binh Tốc Độ
- Tạo bởi: Jarred Sumner, 2022
- Engine: JavaScriptCore (giống Safari)
- Package manager: Tích hợp sẵn (nhanh hơn npm)
- Ecosystem: Tương thích Node.js (hầu hết npm package chạy được)
- Khi nào chọn: Project cần tốc độ, tooling all-in-one, project mới
# Cài đặt và chạy
bun --version
bun run server.ts # Hỗ trợ TypeScript native, không cần configDeno — Lựa Chọn An Toàn
- Tạo bởi: Ryan Dahl, 2018 (đúng vậy, cùng tác giả Node.js)
- Engine: V8
- Package manager: Import URL +
deno.jsonimports map - Ecosystem: Đang phát triển, có lớp tương thích Node.js
- Khi nào chọn: Project ưu tiên bảo mật, module system sạch, web standard
# Cài đặt và chạy
deno --version
deno run --allow-net --allow-read server.tsNên Chọn Cái Nào?
| Tiêu chí | Node.js | Bun | Deno |
|---|---|---|---|
| Thị trường việc làm | 🟢 Chiếm ưu thế | 🟡 Đang phát triển | 🟡 Ngách |
| Tài liệu học | 🟢 Phong phú | 🟡 Trung bình | 🟡 Trung bình |
| Tốc độ | 🟡 Tốt | 🟢 Nhanh nhất | 🟢 Nhanh |
| TypeScript | 🟡 Cần setup | 🟢 Native | 🟢 Native |
| Tương thích npm | 🟢 Hoàn toàn | 🟢 Gần hoàn toàn | 🟡 Qua compat layer |
| Mô hình bảo mật | 🟡 Mở mặc định | 🟡 Mở mặc định | 🟢 Permission-based |
Khuyến nghị cho frontend engineer: Bắt đầu với Node.js. Nhiều tutorial nhất, cộng đồng lớn nhất, và nhiều job listing nhất. Khi đã thoải mái, thử Bun hoặc Deno — chuyển đổi giữa chúng rất đơn giản vì code gần như giống nhau.
Phần 4: Lộ Trình Học
Đây là kế hoạch từng tuần để đi từ frontend engineer thành backend-capable developer. Không cần nghỉ việc — 1-2 tiếng mỗi ngày là đủ.
Tuần 1-2: Node.js Cơ Bản
Mục tiêu: Hiểu cách Node.js hoạt động và xây HTTP server cơ bản.
// server.js — Server backend đầu tiên của bạn
import { createServer } from 'node:http';
const server = createServer((req, res) => {
if (req.url === '/api/hello' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello from the backend!' }));
return;
}
res.writeHead(404);
res.end('Not found');
});
server.listen(3001, () => {
console.log('Server running on http://localhost:3001');
});Cần học:
- Module tích hợp Node.js:
http,fs,path,crypto - Event loop (bạn đã biết từ browser JS — cùng khái niệm, khác API)
- CommonJS vs ESM (dùng ESM — bạn đã biết từ frontend)
- Đọc biến môi trường với
process.env
Tuần 3-4: Chọn Framework
http.createServer() thuần tốt để học nhưng không thực tế. Chọn framework:
Express.js (khuyến nghị cho việc học):
import express from 'express';
const app = express();
app.use(express.json());
const users = [];
app.get('/api/users', (req, res) => {
res.json(users);
});
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' });
}
const user = { id: Date.now(), name, email };
users.push(user);
res.status(201).json(user);
});
app.listen(3001, () => {
console.log('API running on http://localhost:3001');
});Các framework khác nên biết:
| Framework | Phong cách | Phù hợp cho |
|---|---|---|
| Express | Tối giản, middleware-based | Học, API nhỏ |
| Fastify | Tập trung hiệu năng | API thông lượng cao |
| NestJS | Full framework, lấy cảm hứng từ Angular | App enterprise (quen thuộc nếu biết Angular!) |
| Hono | Siêu nhẹ, multi-runtime | Edge function, Bun/Deno/Cloudflare |
Nếu bạn là Angular developer, NestJS sẽ quen như ở nhà — dùng decorator, dependency injection, module, và cấu trúc project tương tự. Quá trình chuyển đổi mượt mà đáng ngạc nhiên.
Tuần 5-6: Tích Hợp Database
Đã đến lúc thay array trong memory bằng database thật.
Lộ trình khuyến nghị: PostgreSQL + Prisma ORM
npm install prisma @prisma/client
npx prisma initĐịnh nghĩa schema:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
}Dùng trong API:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// GET /api/users
app.get('/api/users', async (req, res) => {
const users = await prisma.user.findMany({
include: { posts: true },
});
res.json(users);
});
// POST /api/users
app.post('/api/users', async (req, res) => {
try {
const user = await prisma.user.create({
data: { name: req.body.name, email: req.body.email },
});
res.status(201).json(user);
} catch (error) {
if (error.code === 'P2002') {
return res.status(409).json({ error: 'Email already exists' });
}
res.status(500).json({ error: 'Internal server error' });
}
});Cái này sẽ quen lắm — API của Prisma đọc như TypeScript, không phải SQL. Bạn có đầy đủ autocompletion và type safety.
Tuần 7-8: Authentication & Middleware
Thêm JWT authentication để bảo vệ route:
import jwt from 'jsonwebtoken';
// Middleware — nghĩ như higher-order component của React, nhưng cho route
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next(); // Chuyển sang handler tiếp theo
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}
// Route được bảo vệ
app.get('/api/profile', authenticate, async (req, res) => {
const user = await prisma.user.findUnique({
where: { id: req.user.id },
});
res.json(user);
});Khái niệm quan trọng cho frontend dev: Middleware giống chuỗi hàm xử lý request trước khi đến route handler. Nghĩ như pipeline — tương tự RxJS pipe trong Angular hoặc middleware trong Redux.
Tuần 9-10: Testing & Xử Lý Lỗi
Viết test cho API. Nếu bạn đã dùng Jest hoặc Vitest ở frontend, công cụ test y chang:
// users.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import { app } from './app';
describe('Users API', () => {
it('should create a new user', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Jane', email: '[email protected]' });
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe('Jane');
});
it('should return 400 for missing fields', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Jane' });
expect(response.status).toBe(400);
});
});Tuần 11-12: Deployment
Deploy API ra thế giới thực. Các lựa chọn:
| Nền tảng | Độ khó | Chi phí | Phù hợp cho |
|---|---|---|---|
| Railway | Dễ | Free tier | Prototype nhanh |
| Render | Dễ | Free tier | Side project |
| Fly.io | Trung bình | Free tier | Phân phối toàn cầu |
| AWS/GCP/Azure | Khó | Pay-as-you-go | Production quy mô lớn |
| Docker + VPS | Trung bình | $5-10/tháng | Toàn quyền kiểm soát |
Checklist deployment tối thiểu:
✅ Biến môi trường đã cấu hình (không có secret trong code)
✅ Connection string database đã set
✅ CORS đã cấu hình cho domain frontend
✅ Xử lý lỗi không lộ stack trace
✅ Endpoint health check (GET /health) hoạt động
✅ Logging đã setup để debug lỗi production
Phần 5: Lỗi Phổ Biến Frontend Engineer Hay Mắc
Lỗi 1: Coi Server Như Browser
// ❌ SAI: State toàn cục có thể thay đổi (chạy trên browser ok, thảm họa trên server)
let currentUser = null;
app.post('/api/login', (req, res) => {
currentUser = req.body.user; // Chia sẻ giữa TẤT CẢ request!
res.json({ success: true });
});
// ✅ ĐÚNG: Mỗi request là độc lập
app.post('/api/login', (req, res) => {
const user = req.body.user; // Phạm vi chỉ trong request này
const token = generateToken(user);
res.json({ token });
});Ở frontend, bạn có một user. Ở backend, bạn có hàng nghìn user đồng thời chia sẻ cùng process. Đừng lưu data per-request vào biến module-level.
Lỗi 2: Không Xử Lý Lỗi Đúng Cách
// ❌ SAI: Lỗi không xử lý làm crash server
app.get('/api/users/:id', async (req, res) => {
const user = await prisma.user.findUnique({ where: { id: +req.params.id } });
res.json(user); // Crash nếu user null hoặc id không phải số
});
// ✅ ĐÚNG: Xử lý mọi trường hợp lỗi
app.get('/api/users/:id', async (req, res) => {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) {
return res.status(400).json({ error: 'Invalid user ID' });
}
const user = await prisma.user.findUnique({ where: { id } });
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
console.error('Failed to fetch user:', error);
res.status(500).json({ error: 'Internal server error' });
}
});Ở frontend, lỗi hiện màn hình trắng. Ở backend, lỗi không xử lý sập luôn cả server cho tất cả user.
Lỗi 3: Block Event Loop
// ❌ SAI: Đọc file đồng bộ block TẤT CẢ request
import { readFileSync } from 'node:fs';
app.get('/api/report', (req, res) => {
const data = readFileSync('./large-report.csv', 'utf-8'); // Block!
res.json(processReport(data));
});
// ✅ ĐÚNG: Dùng thao tác bất đồng bộ
import { readFile } from 'node:fs/promises';
app.get('/api/report', async (req, res) => {
const data = await readFile('./large-report.csv', 'utf-8'); // Không block
res.json(processReport(data));
});Lỗi 4: Quên Validate Input
Ở frontend, bạn validate cho UX. Ở backend, bạn validate cho bảo mật.
// ❌ SAI: Tin tưởng user input
app.post('/api/users', async (req, res) => {
const user = await prisma.user.create({ data: req.body }); // Nhận bất cứ gì!
res.json(user);
});
// ✅ ĐÚNG: Validate tất cả
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
});
app.post('/api/users', async (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
const user = await prisma.user.create({ data: result.data });
res.status(201).json(user);
});Bonus: Bạn có thể chia sẻ Zod schema giữa frontend và backend — validate một lần, dùng mọi nơi.
Lỗi 5: Không Hiểu Concurrency
// ❌ SAI: Tư duy tuần tự khi request chồng chéo
let counter = 0;
app.post('/api/increment', async (req, res) => {
counter++; // Race condition với request đồng thời!
res.json({ counter });
});
// ✅ ĐÚNG: Dùng thao tác atomic trong database
app.post('/api/increment', async (req, res) => {
const result = await prisma.counter.update({
where: { id: 1 },
data: { value: { increment: 1 } },
});
res.json({ counter: result.value });
});Phần 6: Project Thực Hành Đầu Tiên
Xây Bookmark API — backend service cho phép user lưu và tổ chức link. Project này cover mọi khái niệm trong lộ trình.
Tính năng:
- Đăng ký và đăng nhập (JWT auth)
- CRUD bookmark
- Tổ chức theo tag
- Chức năng tìm kiếm
- Validate input với Zod
Tech Stack:
- Runtime: Node.js
- Framework: Express.js
- Database: PostgreSQL + Prisma
- Auth: JWT + bcrypt
- Validation: Zod
- Testing: Vitest + Supertest
Cấu Trúc Project:
bookmark-api/
├── src/
│ ├── routes/
│ │ ├── auth.ts # Login, register
│ │ ├── bookmarks.ts # CRUD cho bookmark
│ │ └── tags.ts # Quản lý tag
│ ├── middleware/
│ │ ├── auth.ts # Xác thực JWT
│ │ ├── validate.ts # Validate Zod
│ │ └── errorHandler.ts # Xử lý lỗi toàn cục
│ ├── schemas/
│ │ └── bookmark.ts # Zod schema
│ ├── app.ts # Setup Express app
│ └── server.ts # Entry point server
├── prisma/
│ └── schema.prisma # Schema database
├── tests/
│ └── bookmarks.test.ts # Test API
├── .env # Biến môi trường
├── package.json
└── tsconfig.jsonCấu trúc này sẽ rất quen thuộc nếu bạn đã làm Angular hoặc React project có tổ chức tốt. Pattern tách concern thành route, middleware, và schema map trực tiếp sang cách bạn tổ chức component, guard, và service trong frontend app.
Tổng Kết
✅ Bạn không bắt đầu từ số 0 — JavaScript, TypeScript, async pattern, npm, và kiến thức JSON chuyển thẳng sang
✅ Những khái niệm mới chính là: HTTP từ phía server, database, authentication, và truy cập file system
✅ Bắt đầu với Node.js + Express — ecosystem lớn nhất và tài liệu học nhiều nhất
✅ NestJS giống Angular — nếu bạn là Angular dev, chuyển sang NestJS mượt mà đáng ngạc nhiên
✅ Dùng Prisma cho database — biến SQL thành TypeScript với đầy đủ autocompletion
✅ Mỗi request là độc lập — đừng dùng state toàn cục có thể thay đổi trên server
✅ Validate mọi input — validate frontend cho UX, validate backend cho bảo mật
✅ Xây project thật — lý thuyết chỉ đưa bạn đến một mức, hãy xây API hoàn chỉnh end-to-end
✅ Khoảng cách nhỏ hơn bạn nghĩ — hầu hết frontend engineer có thể xây production API trong 2-3 tháng
Đọc Thêm
Nếu muốn tìm hiểu sâu hơn các chủ đề trong bài:
- JavaScript: Browser vs Server — What's Actually Different? — hiểu sự khác biệt cốt lõi giữa hai môi trường
- Getting Started with Express.js — xây Express.js server đầu tiên từng bước
- Express.js Middleware Deep Dive — nắm vững middleware pattern cho production API
Tiếp Theo Là Gì?
Bạn không cần ai cho phép để trở thành "backend engineer." Bạn là JavaScript developer — backend chỉ là một phòng khác trong cùng ngôi nhà.
Chọn một thứ từ lộ trình này, dành một tiếng cho nó hôm nay, và tiếp tục ngày mai. Vài tuần nữa, bạn sẽ tự hỏi sao không làm sớm hơn.
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.