Middleware trong Express là một hàm có chữ ký (req, res, next), chạy trong khoảng từ lúc request đến cho tới lúc response được gửi đi. Mỗi middleware có thể đọc hoặc thay đổi req và res, kết thúc request bằng cách gửi response, hoặc gọi next() để chuyển request cho hàm kế tiếp trong hàng. app.use(fn) thêm middleware cho mọi request, và Express chạy mọi thứ theo đúng thứ tự bạn đăng ký.
Với mỗi request, Console ghi ra ba bước theo thứ tự, và route đọc req.startedAt, một thuộc tính do logger thêm vào. Gửi GET /missing: hai middleware vẫn ghi log, vì app.use() khớp với mọi đường dẫn, rồi Express trả về 404 vì không có route nào khớp.
Quên gọi next()
Một middleware không gửi response mà cũng không gọi next() sẽ để request bị treo. Client chờ đến khi hết thời gian, và log không có gì, nên đây là một bug middleware rất hay gặp. Xóa next(); khỏi logger ở trên rồi gửi lại GET /: request không bao giờ nhận được câu trả lời.
Quy tắc rất đơn giản: mọi nhánh đi qua một middleware đều kết thúc bằng đúng một trong những cách sau:
next()để đi tiếp,next(err)để nhảy đến error handler (xem xử lý lỗi trong Express),- một response như
res.json()hoặcres.status(401).send().
Gọi next() sau khi đã gửi response cũng là bug. Handler tiếp theo sẽ cố gửi response thứ hai và Express ném ra ERR_HTTP_HEADERS_SENT. Hãy viết return res.status(401).json(...) để hàm dừng ngay tại đó.
Middleware kiểm tra quyền chặn request
Middleware từ chối một request sẽ tự gửi response và không bao giờ gọi next(). Truyền nó làm đối số bổ sung cho một route để chỉ bảo vệ route đó:
Request đầu tiên nhận 401 với {"error":"Missing or wrong API key"}. Thêm header x-api-key: secret123 vào thanh request rồi gửi lại: lần này câu trả lời là {"users":42,"caller":"reporting-app"}. /public không bao giờ chạy bước kiểm tra này.
Middleware chạy ở đâu
| Bạn viết | Chạy cho |
|---|---|
app.use(fn) | Mọi request, mọi phương thức |
app.use('/api', fn) | Các đường dẫn bắt đầu bằng /api |
app.get('/admin', fn, handler) | Chỉ GET /admin, trước handler |
router.use(fn) | Mọi route của router đó |
Thứ tự là một phần của ý nghĩa. Một logger đăng ký sau các route sẽ không bao giờ ghi log những request mà các route đó đã trả lời, vì route gửi response thì không gọi next(). Hãy đăng ký middleware dùng cho cả app như ghi log, parse body và CORS trước, rồi đến các route, cuối cùng là handler 404 và error handler.
Middleware có sẵn
Express kèm theo vài hàm middleware mà bạn thêm bằng app.use(). Các body parser điền giá trị cho req.body; nếu không có chúng, req.body là undefined.
| Middleware | Chức năng |
|---|---|
express.json() | Parse body dạng JSON (Content-Type: application/json) |
express.urlencoded() | Parse dữ liệu gửi từ form HTML (name=Ada&age=36) |
express.text() | Đưa body dạng văn bản thuần vào req.body dưới dạng chuỗi |
express.static('public') | Phục vụ các tệp từ một thư mục |
Gửi POST /echo với body {"name": "Ada"} thì câu trả lời có "body":{"name":"Ada"}. Xóa dòng express.json(), gửi lại, và body biến mất khỏi câu trả lời: req.body là undefined, và JSON.stringify bỏ qua các giá trị undefined. Các package như cors, helmet và morgan cũng là middleware: bạn npm install chúng rồi truyền vào app.use() theo cùng cách. Trang express.static hướng dẫn phục vụ tệp.
Lỗi thường gặp
Gọi middleware thay vì truyền nó vào. app.use(logger()) chạy logger một lần lúc khởi động và đăng ký giá trị trả về của nó. Hãy viết app.use(logger). (express.json() có cặp ngoặc vì nó trả về một hàm middleware.)
Thêm body parser sau các route. Các route chạy trước và thấy req.body là undefined.
Tưởng app.use('/admin', fn) chỉ khớp một đường dẫn. Nó còn chạy cho /admin/users và mọi đường dẫn khác nằm dưới /admin, với mọi phương thức. Để bảo vệ một route duy nhất, hãy truyền middleware vào app.get('/admin', fn, handler).
Câu hỏi thường gặp
Middleware và route handler khác nhau thế nào?
Về mặt kỹ thuật thì không khác gì: cả hai đều là hàm nhận (req, res, next). Theo quy ước, route handler kết thúc request bằng một response, còn middleware thường làm một việc dùng chung rồi gọi next().
Middleware có thể là hàm async không?
Có. Ở Express 5, một middleware async ném lỗi hoặc trả về promise bị reject sẽ tự động chuyển lỗi đến error handler. Hãy await những gì cần rồi gọi next().
next('route') làm gì?
Nó bỏ qua các handler còn lại của route hiện tại và chuyển sang route tiếp theo khớp với request. Nó chỉ hoạt động trong handler được thêm bằng app.get(), app.post() và các hàm theo phương thức khác, không hoạt động trong app.use().
Làm sao bỏ qua một middleware cho một route?
Đăng ký route đó trước middleware để nó trả lời trước, hoặc kiểm tra đường dẫn ngay trong middleware (if (req.path === '/health') return next();). Gắn middleware theo từng router hoặc từng route thay vì cho cả app thường giúp bạn không cần làm vậy.