Bài 6. Express 5: định tuyến, middleware, giao diện

Express là framework web tối giản phổ biến nhất của Node.js. Bài này học Express 5: định tuyến, tham số, middleware, phục vụ tệp tĩnh, xử lý form, render giao diện bằng EJS và xử lý lỗi tập trung.

55 phútTrung cấp
Bạn sẽ học được
  • Tạo ứng dụng Express, định tuyến theo phương thức
  • Đọc req.params, req.query, req.body
  • Viết và dùng middleware, Router
  • Render EJS, phục vụ tĩnh, xử lý 404 và lỗi
Thuật ngữ cần biết
Middleware
Hàm (req, res, next) chạy giữa yêu cầu và phản hồi.
Router
Nhóm các tuyến có chung tiền tố.
Template engine
Sinh HTML từ mẫu + dữ liệu (EJS, Pug, Handlebars).

Một số khái niệm

Một yêu cầu đi qua chuỗi middleware theo thứ tự khai báo: ghi log → đọc body → kiểm tra đăng nhập → tuyến xử lý. Mỗi middleware hoặc trả phản hồi, hoặc gọi next() để chuyển tiếp. Express 5 hỗ trợ hàm async: lỗi ném ra trong hàm async tự được chuyển tới middleware xử lý lỗi.

Ứng dụng Express

npm install express ejs
// src/app.js
import express from 'express';
const app = express();

app.set('view engine', 'ejs');
app.set('views', 'src/views');
app.use(express.static('public'));            // tệp tĩnh: /css/style.css
app.use(express.urlencoded({ extended: true })); // body từ form
app.use(express.json());                      // body JSON

app.use((req, res, next) => {                 // middleware ghi log
  const bd = Date.now();
  res.on('finish', () => console.log(req.method, req.originalUrl, res.statusCode, Date.now() - bd + 'ms'));
  next();
});

const ds = [{ id: 1, ten: 'Lò Văn An', lop: 'K66 Tin' }];

app.get('/', (req, res) => res.render('index', { tieuDe: 'Trang chủ', ds }));
app.get('/sinhvien/:id', (req, res) => {
  const sv = ds.find(s => s.id === Number(req.params.id));
  if (!sv) return res.status(404).render('404');
  res.render('chi-tiet', { sv });
});
app.post('/sinhvien', (req, res) => {
  const ten = (req.body.ten ?? '').trim();
  if (ten.length < 3) return res.status(422).render('index', { tieuDe: 'Trang chủ', ds, loi: 'Tên quá ngắn' });
  ds.push({ id: ds.length + 1, ten, lop: req.body.lop });
  res.redirect(303, '/');                     // PRG
});

app.use((req, res) => res.status(404).render('404'));      // 404
app.use((err, req, res, next) => {                          // lỗi tập trung
  console.error(err);
  res.status(500).send('Đã có lỗi xảy ra');
});
app.listen(process.env.PORT ?? 3000);

Giao diện EJS

<!-- src/views/index.ejs -->
<!doctype html><html lang="vi"><head><meta charset="utf-8"><title><%= tieuDe %></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"></head>
<body class="container py-4">
  <h1><%= tieuDe %></h1>
  <% if (locals.loi) { %><div class="alert alert-danger"><%= loi %></div><% } %>
  <ul class="list-group mb-3">
    <% ds.forEach(sv => { %>
      <li class="list-group-item"><a href="/sinhvien/<%= sv.id %>"><%= sv.ten %></a> – <%= sv.lop %></li>
    <% }) %>
  </ul>
  <form method="post" action="/sinhvien" class="d-flex gap-2"><input name="ten" class="form-control" placeholder="Họ tên"><input name="lop" class="form-control" placeholder="Lớp"><button class="btn btn-success">Thêm</button></form>
</body></html>

<%= %> in giá trị đã mã hóa HTML (chống XSS); <% %> chạy mã JS. Giao diện dùng Bootstrap 5 (loạt Bootstrap 5).

Router tách tuyến

// src/routes/sinhvien.js
import { Router } from 'express';
const r = Router();
r.get('/', (req, res) => res.json(ds));
r.get('/:id', (req, res) => res.json(ds.find(s => s.id == req.params.id) ?? null));
export default r;

// app.js
import sinhVienRouter from './routes/sinhvien.js';
app.use('/api/sinhvien', sinhVienRouter);
Mẹo hay

Cấu trúc thư mục gợi ý: src/routes, src/controllers, src/models, src/views, src/middlewares: chính là mô hình MVC (xem loạt PHP Bài 17).

Lưu ý

Thứ tự app.use quan trọng: middleware 404 phải đặt sau mọi tuyến; middleware lỗi (4 tham số) đặt cuối cùng.

Thực hành
  1. Tạo ứng dụng Express hiển thị danh sách sinh viên bằng EJS.
  2. Thêm trang chi tiết /sinhvien/:id và trang 404.
  3. Viết middleware chặn truy cập /admin nếu thiếu query ?key=.
  4. Tách tuyến API vào Router riêng.
Tóm tắt bài học
  • app.get/post/put/delete(đường-dẫn, xử-lý).
  • req.params, req.query, req.body; res.json, res.render, res.redirect.
  • Middleware (req, res, next), Router, static.
  • Xử lý 404 và lỗi tập trung ở cuối.