Kết nối MongoDB và xây dựng CRUD API
- 24-07-2026
- Toanngo92
- 0 Comments
Trong bài trước, chúng ta đã xây dựng API với Express. Bài này bổ sung mảnh ghép quan trọng của ứng dụng thực tế: lưu dữ liệu lâu dài trong MongoDB. Bạn sẽ kết nối Node.js với MongoDB, thao tác CRUD và hoàn thiện API quản lý sách có thể kiểm thử bằng curl hoặc Postman.

Mục lục
1. Mục tiêu bài học
- Hiểu vai trò của driver khi Node.js giao tiếp với cơ sở dữ liệu.
- Kết nối bằng MongoDB Node.js Driver chính thức.
- Dùng insertOne, find, findOne, updateOne và deleteOne.
- Xử lý ObjectId, mã trạng thái HTTP và lỗi đầu vào.
- Xây dựng REST API CRUD bằng Express và MongoDB.
2. Node.js làm việc với cơ sở dữ liệu như thế nào?
Một driver đứng giữa ứng dụng và máy chủ cơ sở dữ liệu, chuyển lời gọi JavaScript thành giao thức MongoDB, quản lý kết nối và chuyển kết quả trở lại cho Node.js. Node.js hỗ trợ cả cơ sở dữ liệu quan hệ như MySQL, PostgreSQL, SQL Server và phi quan hệ như MongoDB, Redis hoặc Cassandra. MongoDB phù hợp với JavaScript vì dữ liệu document rất gần với object.

3. Chuẩn bị MongoDB và dự án
Bạn có thể dùng MongoDB Community trên máy hoặc MongoDB Atlas. Với môi trường cục bộ, URI thường là mongodb://127.0.0.1:27017. Không đưa mật khẩu hoặc connection string thật lên Git.
mkdir node-mongodb-api
cd node-mongodb-api
npm init -y
npm install express mongodb dotenv
# .env
MONGODB_URI=mongodb://127.0.0.1:27017
PORT=3000

4. Tạo kết nối dùng chung
Mỗi tiến trình nên tạo một MongoClient rồi tái sử dụng. Driver đã có connection pool; tạo client mới trong từng request vừa chậm vừa làm tăng số kết nối.
// db.js
const { MongoClient } = require('mongodb');
const uri = process.env.MONGODB_URI ?? 'mongodb://127.0.0.1:27017';
const client = new MongoClient(uri);
let database;
async function connectDatabase() {
await client.connect();
database = client.db('library');
await database.command({ ping: 1 });
console.log('Đã kết nối MongoDB');
}
function getDatabase() {
if (!database) throw new Error('MongoDB chưa được kết nối');
return database;
}
module.exports = { client, connectDatabase, getDatabase };
5. CRUD trong MongoDB
Dữ liệu MongoDB được tổ chức thành database, collection và document. Ví dụ dùng database library, collection books. Mỗi document mặc định có trường _id kiểu ObjectId.

6. Create và Read
const books = db.collection('books');
const result = await books.insertOne({
name: 'Node.js thực chiến',
author: 'Học Viết Code',
price: 180000,
createdAt: new Date()
});
console.log(result.insertedId);
const allBooks = await books
.find({})
.sort({ createdAt: -1 })
.limit(20)
.toArray();
find() trả về cursor, vì vậy cần gọi toArray() để nhận mảng kết quả. Khi chỉ cần một document, dùng findOne().
7. ObjectId, Update và Delete
req.params.id là chuỗi, còn _id mặc định là ObjectId. Luôn kiểm tra định dạng trước khi chuyển đổi để API có thể trả lỗi 400 rõ ràng.
const { ObjectId } = require('mongodb');
if (!ObjectId.isValid(id)) {
throw new Error('ID không hợp lệ');
}
const _id = new ObjectId(id);
await books.updateOne(
{ _id },
{ $set: { price: 210000, updatedAt: new Date() } }
);
await books.deleteOne({ _id });
Với cập nhật, kiểm tra matchedCount; với xóa, kiểm tra deletedCount. Nếu bằng 0, API nên trả 404.
8. Xây dựng REST API Express
require('dotenv').config();
const express = require('express');
const { ObjectId } = require('mongodb');
const { connectDatabase, getDatabase } = require('./db');
const app = express();
app.use(express.json());
const books = () => getDatabase().collection('books');
app.get('/books', async (req, res, next) => {
try {
res.json(await books().find({}).limit(100).toArray());
} catch (error) { next(error); }
});
app.get('/books/:id', async (req, res, next) => {
try {
if (!ObjectId.isValid(req.params.id)) {
return res.status(400).json({ message: 'ID không hợp lệ' });
}
const book = await books().findOne({
_id: new ObjectId(req.params.id)
});
if (!book) return res.status(404).json({ message: 'Không tìm thấy sách' });
res.json(book);
} catch (error) { next(error); }
});
9. API tạo, sửa và xóa
app.post('/books', async (req, res, next) => {
try {
const { name, author, price } = req.body;
if (!name || !author || !Number.isFinite(price)) {
return res.status(400).json({ message: 'Dữ liệu không hợp lệ' });
}
const book = { name, author, price, createdAt: new Date() };
const result = await books().insertOne(book);
res.status(201).json({ ...book, _id: result.insertedId });
} catch (error) { next(error); }
});
app.put('/books/:id', async (req, res, next) => {
try {
if (!ObjectId.isValid(req.params.id)) return res.status(400).json({ message: 'ID không hợp lệ' });
const result = await books().updateOne(
{ _id: new ObjectId(req.params.id) },
{ $set: { ...req.body, updatedAt: new Date() } }
);
if (!result.matchedCount) return res.status(404).json({ message: 'Không tìm thấy sách' });
res.json({ message: 'Cập nhật thành công' });
} catch (error) { next(error); }
});
app.delete('/books/:id', async (req, res, next) => {
try {
if (!ObjectId.isValid(req.params.id)) return res.status(400).json({ message: 'ID không hợp lệ' });
const result = await books().deleteOne({ _id: new ObjectId(req.params.id) });
if (!result.deletedCount) return res.status(404).json({ message: 'Không tìm thấy sách' });
res.status(204).end();
} catch (error) { next(error); }
});

10. Khởi động và xử lý lỗi tập trung
app.use((error, req, res, next) => {
console.error(error);
res.status(500).json({ message: 'Lỗi máy chủ' });
});
async function start() {
await connectDatabase();
app.listen(process.env.PORT ?? 3000, () => {
console.log('API chạy tại http://localhost:3000');
});
}
start().catch(console.error);
Ứng dụng chỉ lắng nghe sau khi kết nối cơ sở dữ liệu thành công. Khi tiến trình dừng, hãy gọi client.close() để giải phóng tài nguyên.
11. Kiểm thử API bằng curl
curl http://localhost:3000/books
curl -X POST http://localhost:3000/books \
-H "Content-Type: application/json" \
-d '{"name":"Node.js căn bản","author":"An","price":150000}'
curl -X DELETE http://localhost:3000/books/BOOK_ID

12. Lỗi thường gặp
- ECONNREFUSED: MongoDB chưa chạy hoặc URI sai.
- Authentication failed: sai tài khoản, mật khẩu hoặc authentication database.
- Không tìm thấy document: quên chuyển chuỗi ID thành ObjectId.
- req.body là undefined: thiếu
app.use(express.json()). - Quá nhiều kết nối: tạo MongoClient trong từng request.
- API chậm: thiếu index, phân trang hoặc giới hạn bản ghi.
13. Bài tập thực hành
- Thêm trường category và endpoint lọc sách theo thể loại.
- Thêm phân trang bằng page, limit, skip() và limit().
- Tạo unique index cho ISBN và xử lý lỗi trùng dữ liệu.
- Viết middleware kiểm tra dữ liệu đầu vào.
- Tách router, controller và repository thành các module riêng.
Tổng kết
Bạn đã đi từ kết nối MongoDB đến REST API CRUD hoàn chỉnh. Hãy giữ connection string trong biến môi trường, tái sử dụng một MongoClient cho mỗi tiến trình và luôn kiểm tra dữ liệu đầu vào cùng ObjectId. Đây là nền tảng để xây dựng ứng dụng Node.js có xác thực, phân trang và triển khai thực tế.
