RESTful API
RESTful API trong Node.js là một chuẩn kiến trúc thiết kế hệ thống API dựa trên các phương thức của giao thức HTTP (GET, POST, PUT, DELETE) để quản lý các tài nguyên (Resources) [shopping]. Một hệ thống RESTful API chuẩn chỉnh yêu cầu tài nguyên phải được định danh qua URL và các hành […]
RESTful API trong Node.js là một chuẩn kiến trúc thiết kế hệ thống API dựa trên các phương thức của giao thức HTTP (GET, POST, PUT, DELETE) để quản lý các tài nguyên (Resources) [shopping]. Một hệ thống RESTful API chuẩn chỉnh yêu cầu tài nguyên phải được định danh qua URL và các hành động được phân tách rõ ràng bằng HTTP Verbs.
Để xây dựng một hệ thống RESTful API chuyên nghiệp, lập trình viên thực tế luôn áp dụng mô hình phân tầng độc lập: Routes (Định tuyến) → Controllers (Logic xử lý) → Models (Dữ liệu).
Quy chuẩn đặt tên URL và HTTP Methods (Mẫu chuẩn REST)
Giả sử chúng ta quản lý tài nguyên là “Bài viết” (posts), URL phải luôn là một danh từ số nhiều [shopping]. Tuyệt đối không chèn động từ vào URL (Ví dụ sai: /get-all-posts, /deletePost).
| Hành động | HTTP Method | URL | Ý nghĩa | Trạng thái HTTP trả về |
| Read (All) | GET | /api/v1/posts | Lấy danh sách toàn bộ bài viết | 200 OK |
| Read (Detail) | GET | /api/v1/posts/:id | Lấy chi tiết một bài viết theo ID | 200 OK hoặc 404 |
| Create | POST | /api/v1/posts | Tạo một bài viết mới | 201 Created |
| Update | PATCH | /api/v1/posts/:id | Sửa một vài trường của bài viết | 200 OK |
| Delete | DELETE | /api/v1/posts/:id | Xóa bài viết khỏi hệ thống | 200 OK hoặc 204 |
Trọn bộ mã nguồn RESTful API chuẩn kiến trúc phân tầng
Bước 1: Thiết kế Tầng Model (Khuôn mẫu dữ liệu)
// file: models/post.model.js
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: { type: String, required: true },
content: { type: String, required: true },
author: { type: String, required: true }
}, { timestamps: true });
module.exports = mongoose.model('Post', postSchema);
Bước 2: Thiết kế Tầng Controller (Logic xử lý chính)
Tầng này tiếp nhận yêu cầu, tương tác với Database và trả về định dạng cấu trúc JSON đồng nhất kèm theo mã trạng thái HTTP (Status Code) chính xác [shopping].
// file: controllers/post.controller.js
const Post = require('../models/post.model');
// 1. GET ALL
const getAllPosts = async (req, res) => {
try {
const posts = await Post.find();
res.status(200).json({ status: 'success', results: posts.length, data: posts });
} catch (error) {
res.status(500).json({ status: 'error', message: error.message });
}
};
// 2. GET DETAIL
const getPostById = async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) return res.status(404).json({ status: 'fail', message: 'Không tìm thấy bài viết!' });
res.status(200).json({ status: 'success', data: post });
} catch (error) {
res.status(500).json({ status: 'error', message: error.message });
}
};
// 3. CREATE
const createPost = async (req, res) => {
try {
const newPost = await Post.create(req.body);
res.status(201).json({ status: 'success', data: newPost });
} catch (error) {
res.status(400).json({ status: 'fail', message: error.message });
}
};
// 4. UPDATE (PATCH)
const updatePost = async (req, res) => {
try {
const updatedPost = await Post.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!updatedPost) return res.status(404).json({ status: 'fail', message: 'Không tìm thấy bài viết để sửa!' });
res.status(200).json({ status: 'success', data: updatedPost });
} catch (error) {
res.status(400).json({ status: 'fail', message: error.message });
}
};
// 5. DELETE
const deletePost = async (req, res) => {
try {
const post = await Post.findByIdAndDelete(req.params.id);
if (!post) return res.status(404).json({ status: 'fail', message: 'Không tìm thấy bài viết để xóa!' });
res.status(200).json({ status: 'success', message: 'Đã xóa bài viết thành công!' });
} catch (error) {
res.status(500).json({ status: 'error', message: error.message });
}
};
module.exports = { getAllPosts, getPostById, createPost, updatePost, deletePost };
Bước 3: Thiết kế Tầng Route (Định tuyến sạch bằng router.route)
Tận dụng tính năng gộp cụm endpoint của Express để rút gọn tệp tin định tuyến:
// file: routes/post.route.js
const express = require('express');
const router = express.Router();
const postController = require('../controllers/post.controller');
// Cụm URL không có tham số ID
router.route('/')
.get(postController.getAllPosts)
.post(postController.createPost);
// Cụm URL bắt buộc có tham số ID
router.route('/:id')
.get(postController.getPostById)
.patch(postController.updatePost)
.delete(postController.deletePost);
module.exports = router;
Bước 4: File cấu hình chạy chính (app.js)
// file: app.js
const express = require('express');
const app = express();
const postRouter = require('./routes/post.route');
app.use(express.json());
// Khai báo tiền tố API phiên bản (Versioning) giúp dễ nâng cấp hệ thống sau này
app.use('/api/v1/posts', postRouter);
// Middleware xử lý lỗi khi client gọi sai URL (404 Not Found)
app.use((req, res, next) => {
res.status(404).json({ status: 'fail', message: `Không tìm thấy đường dẫn ${req.originalUrl} trên máy chủ!` });
});
app.listen(3000, () => console.log('RESTful API đang chạy tại cổng 3000'));
Tiêu chuẩn vàng để API đạt cấp độ Production
- API Versioning: Luôn chèn ký tự phiên bản như /api/v1/… vào đường dẫn URL [shopping]. Khi hệ thống lớn lên và bạn cần sửa đổi toàn bộ cấu trúc logic, bạn chỉ cần phát triển nhánh /api/v2/… mà không làm ảnh hưởng đến các ứng dụng cũ (App di động, Frontend cũ) đang chạy phiên bản v1.
- Đồng nhất cấu trúc phản hồi JSON: Một API chuẩn hóa phải luôn trả về một định dạng bọc ngoài cố định (Ví dụ luôn có trường status: ‘success’ | ‘fail’ | ‘error’ và bọc dữ liệu trong trường data). Điều này giúp lập trình viên Frontend dễ dàng viết hàm bắt lỗi tự động.
- Sử dụng đúng mã trạng thái HTTP: Tuyệt đối không lạm dụng mã 200 OK cho tất cả mọi thứ. Nếu tạo mới thành công phải trả về 201, nếu lỗi dữ liệu đầu vào của client phải là 400, nếu chưa đăng nhập là 401, bị chặn quyền truy cập là 403 và lỗi hệ thống sập nguồn là 500.