Xây dựng website với Express, EJS và MongoDB
- 24-07-2026
- Toanngo92
- 0 Comments
Sau khi đã biết xây dựng API và thao tác MongoDB, chúng ta sẽ ghép các phần lại thành một website hoàn chỉnh. Bài này xây dựng ứng dụng thư viện bằng Node.js, Express, EJS và MongoDB Atlas, sau đó đưa mã nguồn lên GitHub và triển khai trên Render.

Mục lục
1. Website thư viện cần những chức năng gì?
Ứng dụng mẫu có bốn nhóm chức năng: đăng nhập người dùng, đăng ký thành viên mới, xem danh sách sách và quản trị dữ liệu. Ở mức tối thiểu, khách truy cập xem trang đăng nhập; thành viên xem danh sách sách; quản trị viên có thể thêm hoặc chỉnh sửa sách.

2. Kiến trúc ứng dụng
Trình duyệt gửi request tới Express. Router xác định chức năng, controller xử lý nghiệp vụ, MongoDB lưu dữ liệu và EJS kết xuất HTML. Thư mục public chứa CSS, JavaScript phía trình duyệt và hình ảnh. Cách tách lớp này giúp dự án dễ đọc và dễ mở rộng hơn việc đặt toàn bộ mã trong một tệp.
3. Khởi tạo dự án
mkdir library-app
cd library-app
npm init -y
npm install express ejs mongodb dotenv
npm install --save-dev nodemon
Thêm scripts vào package.json:
"scripts": {
"dev": "nodemon app.js",
"start": "node app.js"
}
Render sẽ chạy lệnh npm start, vì vậy script này phải tồn tại và không phụ thuộc nodemon.

4. Tổ chức thư mục
library-app/
├─ app.js
├─ package.json
├─ .env
├─ models/
│ └─ book.repository.js
├─ routes/
│ └─ book.routes.js
├─ views/
│ ├─ index.ejs
│ └─ books.ejs
└─ public/
├─ css/style.css
└─ images/

5. Kết nối MongoDB Atlas
Tạo cluster, database user và cấu hình Network Access trong MongoDB Atlas. Sao chép connection string vào biến môi trường; không ghi trực tiếp tài khoản hoặc mật khẩu trong mã nguồn.
# .env
MONGODB_URI=mongodb+srv://USER:PASSWORD@CLUSTER/library
PORT=3000
const { MongoClient } = require('mongodb');
const client = new MongoClient(process.env.MONGODB_URI);
async function connectDatabase() {
await client.connect();
return client.db('library');
}
Mỗi tiến trình nên tái sử dụng một MongoClient. Không mở kết nối mới trong từng request.
6. Cấu hình Express, EJS và tài nguyên tĩnh
require('dotenv').config();
const path = require('path');
const express = require('express');
const app = express();
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
express.static() phục vụ CSS, ảnh và JavaScript trong public. Khi đường dẫn tuyệt đối được tạo bằng path.join(__dirname, 'public'), ứng dụng không phụ thuộc vào thư mục chạy lệnh.
7. Repository và route danh sách sách
// models/book.repository.js
function createBookRepository(db) {
const books = db.collection('books');
return {
findAll() {
return books.find({}).sort({ name: 1 }).limit(100).toArray();
},
create(book) {
return books.insertOne({ ...book, createdAt: new Date() });
}
};
}
module.exports = createBookRepository;
// routes/book.routes.js
const express = require('express');
module.exports = function createBookRouter(repository) {
const router = express.Router();
router.get('/', async (req, res, next) => {
try {
const books = await repository.findAll();
res.render('books', { title: 'Danh sách sách', books });
} catch (error) { next(error); }
});
return router;
};
8. Tạo giao diện EJS
<!-- views/books.ejs -->
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= title %></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1><%= title %></h1>
<table>
<% books.forEach(book => { %>
<tr>
<td><%= book.name %></td>
<td><%= book.author %></td>
<td><%= book.price %></td>
</tr>
<% }) %>
</table>
</body>
</html>
Cú pháp <%= ... %> của EJS escape dữ liệu trước khi đưa ra HTML. Hạn chế dùng <%- ... %> với dữ liệu do người dùng nhập để tránh XSS.
9. Khởi động ứng dụng đúng cách
async function start() {
const db = await connectDatabase();
const repository = createBookRepository(db);
app.use('/books', createBookRouter(repository));
app.use((error, req, res, next) => {
console.error(error);
res.status(500).render('error', { message: 'Lỗi máy chủ' });
});
const port = process.env.PORT || 3000;
app.listen(port, '0.0.0.0', () => {
console.log('Server chạy tại cổng ' + port);
});
}
start().catch(console.error);
Ứng dụng chỉ lắng nghe sau khi kết nối MongoDB thành công và phải dùng process.env.PORT khi triển khai.
10. Đưa mã nguồn lên GitHub
# .gitignore
node_modules/
.env
git init
git add .
git commit -m "Build library website"
git branch -M main
git remote add origin YOUR_REPOSITORY_URL
git push -u origin main
Kiểm tra lại repository để chắc chắn tệp .env không xuất hiện. Trên Render, các biến bí mật sẽ được khai báo riêng trong phần Environment.
11. Triển khai website Node.js lên Render
- Trong Render Dashboard, chọn New → Web Service.
- Kết nối repository GitHub và chọn branch
main. - Build Command:
npm install. - Start Command:
npm start. - Thêm biến
MONGODB_URItrong Environment. - Tạo Web Service và theo dõi build log.
Mỗi lần push vào branch đã liên kết, Render có thể tự động build và triển khai phiên bản mới. Nếu build thất bại, hãy kiểm tra Node version, start script, biến môi trường và log kết nối MongoDB.

12. Kiểm thử sau triển khai
- Mở URL
onrender.comvà kiểm tra trang chủ. - Đăng nhập bằng tài khoản thử nghiệm.
- Kiểm tra danh sách sách từ MongoDB Atlas.
- Thử thêm hoặc cập nhật dữ liệu với quyền quản trị.
- Kiểm tra CSS, hình ảnh và các liên kết tĩnh.
- Xem log Render nếu trang trả lỗi 500.

13. Lưu ý về bảo mật
Không lưu mật khẩu dạng văn bản thuần; hãy băm bằng bcrypt hoặc Argon2. Session cookie nên bật httpOnly, secure trong production và có thời hạn phù hợp. Kiểm tra dữ liệu đầu vào, phân quyền route quản trị, giới hạn số lần đăng nhập và không hiển thị stack trace cho người dùng.
14. Bài tập thực hành
- Thêm trang chi tiết sách và bộ lọc theo tác giả.
- Thêm form tạo thành viên với kiểm tra dữ liệu.
- Phân trang danh sách sách.
- Viết trang 404 và trang xử lý lỗi.
- Triển khai một Passport Portal tương tự bài tập cuối Session 10.
Tổng kết
Bạn đã đi qua toàn bộ vòng đời một website Node.js: thiết kế chức năng, tổ chức Express, kết xuất EJS, kết nối MongoDB Atlas, quản lý tài nguyên tĩnh, đưa mã lên GitHub và triển khai Render. Đây là nền tảng để phát triển ứng dụng có xác thực, phân quyền và quy trình triển khai tự động.
