Xây dựng RESTful API với Express và HTTP trong nodejs
- 22-07-2026
- Toanngo92
- 0 Comments
API là hợp đồng giúp hai phần mềm giao tiếp với nhau. Trong ứng dụng Node.js, HTTP API thường nhận request từ trình duyệt, ứng dụng di động hoặc dịch vụ khác, xử lý nghiệp vụ rồi trả response dưới dạng JSON. Bài này giải thích mối quan hệ giữa HTTP API và REST, sau đó xây dựng một CRUD API bằng Express.

Mục lục
1. API là gì?
Application Programming Interface là tập hợp quy tắc cho phép một thành phần yêu cầu dữ liệu hoặc chức năng từ thành phần khác. Client chỉ cần biết cách gửi request và đọc response; nó không cần biết server lưu dữ liệu hay xử lý nội bộ như thế nào.

Trong ví dụ thanh toán, website gửi thông tin giao dịch đến dịch vụ thanh toán qua API. Dịch vụ trả kết quả để website thông báo cho người dùng. Cùng một API có thể phục vụ website, ứng dụng di động và hệ thống nội bộ.
2. Các loại API thường gặp trong Node.js
- Core API: API tích hợp sẵn của Node.js như
node:http,node:fsvànode:path. - Third-party API: thư viện npm hoặc dịch vụ do bên khác cung cấp.
- Custom API: API do nhóm phát triển tự xây dựng cho nghiệp vụ riêng.
- Web/HTTP API: API được truy cập qua HTTP hoặc HTTPS.

3. HTTP API và RESTful API
HTTP API là khái niệm rộng: bất kỳ API nào giao tiếp bằng HTTP đều thuộc nhóm này. REST là một kiểu kiến trúc. Một RESTful API dùng tài nguyên, URI, phương thức HTTP, biểu diễn dữ liệu và tính stateless theo cách nhất quán.
Vì vậy, mọi RESTful API chạy trên HTTP đều là HTTP API, nhưng không phải HTTP API nào cũng tuân thủ REST. Ví dụ POST /getStudent vẫn là HTTP API, nhưng thiết kế GET /students/10 thể hiện tài nguyên và ngữ nghĩa HTTP rõ hơn.
Các đặc điểm quan trọng
- Client–server: giao diện client và xử lý dữ liệu server có thể phát triển độc lập.
- Stateless: mỗi request mang đủ thông tin cần thiết; server không suy diễn người dùng chỉ từ kết nối mạng trước đó.
- Uniform interface: tài nguyên có URI ổn định, thao tác thể hiện bằng phương thức HTTP.
- Cacheable: response nói rõ có thể cache hay không.
- Layered system: client có thể đi qua proxy, gateway hoặc load balancer.
4. Phương thức HTTP và CRUD

| Nghiệp vụ | Phương thức | Endpoint |
|---|---|---|
| Lấy danh sách | GET |
/students |
| Lấy một bản ghi | GET |
/students/:id |
| Tạo mới | POST |
/students |
| Thay thế toàn bộ | PUT |
/students/:id |
| Cập nhật một phần | PATCH |
/students/:id |
| Xóa | DELETE |
/students/:id |
GET là phương thức an toàn: request đọc dữ liệu không nên thay đổi trạng thái server. PUT và DELETE có tính idempotent theo ngữ nghĩa HTTP: gửi cùng yêu cầu nhiều lần có hiệu ứng dự kiến tương đương một lần. POST thường không idempotent.
5. Mã trạng thái HTTP cần dùng đúng
200 OK: đọc hoặc cập nhật thành công và có response body.201 Created: tạo tài nguyên thành công.204 No Content: thành công nhưng không trả body, thường dùng khi xóa.400 Bad Request: request sai cú pháp hoặc dữ liệu đầu vào không hợp lệ.404 Not Found: không tìm thấy tài nguyên.409 Conflict: dữ liệu xung đột với trạng thái hiện tại.500 Internal Server Error: lỗi không mong đợi phía server.
Không nên luôn trả 200 rồi đặt lỗi trong JSON. Client, proxy và công cụ giám sát dựa vào status code để hiểu kết quả request.
6. Khởi tạo dự án Express
mkdir student-api
cd student-api
npm init -y
npm install express
Tạo tệp app.js:
const express = require('express');
const app = express();
const PORT = 3000;
app.use(express.json());
let nextId = 4;
const students = [
{ id: 1, name: 'David Miller', age: 12, grade: 'A' },
{ id: 2, name: 'Adam Smith', age: 13, grade: 'B' },
{ id: 3, name: 'John Williams', age: 12, grade: 'A' }
];
express.json() phân tích request có JSON body và gán kết quả vào req.body. Dữ liệu trong mảng chỉ phục vụ học tập; khi khởi động lại server, mọi thay đổi sẽ mất.
7. Route GET
app.get('/students', (req, res) => {
res.json({ data: students });
});
app.get('/students/:id', (req, res) => {
const id = Number(req.params.id);
const student = students.find(item => item.id === id);
if (!student) {
return res.status(404).json({ error: 'Không tìm thấy sinh viên' });
}
res.json({ data: student });
});
req.params.id luôn bắt đầu dưới dạng chuỗi, nên cần chuyển sang số trước khi so sánh với ID dạng số.
8. Kiểm tra dữ liệu đầu vào
function validateStudent(body, { partial = false } = {}) {
const errors = [];
if (!partial || body.name !== undefined) {
if (typeof body.name !== 'string' || body.name.trim().length < 2) {
errors.push('name phải là chuỗi có ít nhất 2 ký tự');
}
}
if (!partial || body.age !== undefined) {
if (!Number.isInteger(body.age) || body.age < 6 || body.age > 100) {
errors.push('age phải là số nguyên từ 6 đến 100');
}
}
if (!partial || body.grade !== undefined) {
if (typeof body.grade !== 'string' || !body.grade.trim()) {
errors.push('grade là trường bắt buộc');
}
}
return errors;
}
9. Route POST tạo tài nguyên
app.post('/students', (req, res) => {
const errors = validateStudent(req.body);
if (errors.length) {
return res.status(400).json({ errors });
}
const student = {
id: nextId++,
name: req.body.name.trim(),
age: req.body.age,
grade: req.body.grade.trim()
};
students.push(student);
res.status(201)
.location(`/students/${student.id}`)
.json({ data: student });
});
Response 201 Created kèm header Location giúp client biết URI của tài nguyên mới.
10. Route PATCH và DELETE
app.patch('/students/:id', (req, res) => {
const student = students.find(item => item.id === Number(req.params.id));
if (!student) {
return res.status(404).json({ error: 'Không tìm thấy sinh viên' });
}
const errors = validateStudent(req.body, { partial: true });
if (errors.length) {
return res.status(400).json({ errors });
}
if (req.body.name !== undefined) student.name = req.body.name.trim();
if (req.body.age !== undefined) student.age = req.body.age;
if (req.body.grade !== undefined) student.grade = req.body.grade.trim();
res.json({ data: student });
});
app.delete('/students/:id', (req, res) => {
const index = students.findIndex(item => item.id === Number(req.params.id));
if (index === -1) {
return res.status(404).json({ error: 'Không tìm thấy sinh viên' });
}
students.splice(index, 1);
res.status(204).end();
});

11. Xử lý route không tồn tại và lỗi server
app.use((req, res) => {
res.status(404).json({ error: 'Endpoint không tồn tại' });
});
app.use((error, req, res, next) => {
console.error(error);
res.status(500).json({ error: 'Lỗi máy chủ' });
});
app.listen(PORT, () => {
console.log(`API chạy tại http://localhost:${PORT}`);
});
Hai middleware này phải đặt sau các route hợp lệ. Trong môi trường production, không trả stack trace hoặc thông tin nội bộ cho client.
12. Kiểm thử bằng curl
curl http://localhost:3000/students
curl http://localhost:3000/students/1
curl -X POST http://localhost:3000/students \
-H "Content-Type: application/json" \
-d '{"name":"Michael Faraday","age":11,"grade":"A"}'
curl -X PATCH http://localhost:3000/students/1 \
-H "Content-Type: application/json" \
-d '{"grade":"A+"}'
curl -i -X DELETE http://localhost:3000/students/2

13. JSON Schema và validation
Khi cấu trúc request lớn hơn, có thể mô tả dữ liệu bằng JSON Schema và dùng thư viện như Ajv để kiểm tra. Schema nên khai báo phiên bản cụ thể, kiểu dữ liệu, thuộc tính bắt buộc và giới hạn. Validation không thay thế việc kiểm tra quyền truy cập hay quy tắc nghiệp vụ.
14. Thực hành tốt khi xây dựng API
- Dùng danh từ số nhiều cho collection:
/students, không dùng động từ như/getStudents. - Trả status code và JSON lỗi nhất quán.
- Giới hạn kích thước body, kiểm tra dữ liệu và không tin input từ client.
- Dùng HTTPS, xác thực và phân quyền cho dữ liệu nhạy cảm.
- Thêm phân trang cho danh sách lớn và versioning khi có thay đổi phá vỡ tương thích.
- Đưa logic nghiệp vụ, truy cập dữ liệu và route sang các lớp riêng khi dự án lớn hơn.
Tổng kết
HTTP cung cấp phương thức, URI, header và status code; REST dùng các thành phần đó theo một kiểu kiến trúc hướng tài nguyên. Với Express, chúng ta đã xây dựng đủ route CRUD, kiểm tra dữ liệu, xử lý 404/500 và thử API bằng curl. Bước tiếp theo là thay mảng trong bộ nhớ bằng MongoDB để dữ liệu được lưu bền vững.
Bài tập
Bài 2: Website bán hàng Node.js + MongoDB
Yêu cầu chung — 20 điểm
Xây dựng website bán hàng sử dụng:Node.js
Express.js
MongoDB
Express API + React
Phần 1: Thiết kế cơ sở dữ liệu — 4 điểm
Tạo các collection sau:products
users
orders
Collection products cần có tối thiểu các trường:
name: String
price: Number
image: String
description: String
stock: Number
Collection users cần có tối thiểu:
username: String
password_hash: String
phone: String
role: String
Collection orders cần lưu:
user_id
items
total
created_at
Yêu cầu dữ liệu mẫu:
Tạo ít nhất 10 sản phẩm mẫu
Tạo ít nhất 1 tài khoản admin
Phần 2: Trang chủ hiển thị sản phẩm — 4 điểm
Xây dựng trang chủ có giao diện bán hàng. Yêu cầu:- Hiển thị danh sách sản phẩm từ MongoDB.
- Mỗi sản phẩm cần hiển thị:
Ảnh sản phẩm
Tên sản phẩm
Giá
Mô tả ngắn
Nút thêm vào giỏ hàng
- Giao diện bố cục rõ ràng, dễ sử dụng.
- Có menu điều hướng:
Trang chủ
Giỏ hàng
Đăng nhập / Đăng xuất
Admin nếu là tài khoản quản trị
Phần 3: Trang quản trị sản phẩm — 5 điểm
Xây dựng trang admin quản lý sản phẩm. Chỉ user có role:admin
mới được truy cập.
Yêu cầu chức năng:
Thêm sản phẩm
Sửa sản phẩm
Xóa sản phẩm
Xem danh sách sản phẩm
Khi thêm/sửa sản phẩm cần nhập:
Tên sản phẩm
Giá
Ảnh
Mô tả
Số lượng tồn kho
Phần 4: Giỏ hàng — 4 điểm
Xây dựng chức năng giỏ hàng. Yêu cầu:- Người dùng có thể thêm sản phẩm vào giỏ hàng.
- Giỏ hàng lưu trong session.
- Trang giỏ hàng hiển thị:
Tên sản phẩm
Giá
Số lượng
Tạm tính
Tổng tiền
- Có chức năng:
Xóa sản phẩm khỏi giỏ hàng
Cập nhật số lượng sản phẩm
Phần 5: Mua hàng / đặt hàng — 3 điểm
Xây dựng chức năng đặt hàng. Yêu cầu:- Người dùng phải đăng nhập mới được đặt hàng.
- Khi đặt hàng, dữ liệu đơn hàng được lưu vào collection
orders. - Sau khi đặt hàng thành công:
Xóa giỏ hàng
Hiển thị thông báo đặt hàng thành công

