Khám phá các mẫu middleware nâng cao trong Express.js để xây dựng ứng dụng web mạnh mẽ, có khả năng mở rộng và dễ bảo trì cho người dùng toàn cầu. Tìm hiểu về xử lý lỗi, xác thực, giới hạn tốc độ, v.v.
Middleware trong Express.js: Nắm vững các Mẫu Nâng cao cho Ứng dụng có Khả năng Mở rộng
Express.js, một framework web tối giản, linh hoạt và nhanh chóng cho Node.js, là nền tảng để xây dựng các ứng dụng web và API. Trọng tâm của nó là khái niệm mạnh mẽ về middleware. Bài viết blog này đi sâu vào các mẫu middleware nâng cao, cung cấp cho bạn kiến thức và ví dụ thực tế để tạo ra các ứng dụng mạnh mẽ, có khả năng mở rộng và dễ bảo trì, phù hợp với người dùng toàn cầu. Chúng ta sẽ khám phá các kỹ thuật xử lý lỗi, xác thực, phân quyền, giới hạn tốc độ và các khía cạnh quan trọng khác của việc xây dựng ứng dụng web hiện đại.
Tìm hiểu về Middleware: Nền tảng
Các hàm middleware trong Express.js là các hàm có quyền truy cập vào đối tượng yêu cầu (req
), đối tượng phản hồi (res
), và hàm middleware tiếp theo trong chu kỳ yêu cầu-phản hồi của ứng dụng. Các hàm middleware có thể thực hiện nhiều tác vụ, bao gồm:
- Thực thi bất kỳ đoạn mã nào.
- Thực hiện thay đổi trên các đối tượng yêu cầu và phản hồi.
- Kết thúc chu kỳ yêu cầu-phản hồi.
- Gọi hàm middleware tiếp theo trong chuỗi.
Về cơ bản, middleware là một đường ống. Mỗi phần của middleware thực hiện chức năng cụ thể của nó, và sau đó, tùy chọn, chuyển quyền điều khiển cho middleware tiếp theo trong chuỗi. Cách tiếp cận mô-đun này thúc đẩy việc tái sử dụng mã, tách biệt các mối quan tâm và kiến trúc ứng dụng sạch sẽ hơn.
Cấu trúc của một Middleware
Một hàm middleware điển hình có cấu trúc như sau:
function myMiddleware(req, res, next) {
// Thực hiện các hành động
// Ví dụ: Ghi lại thông tin yêu cầu
console.log(`Yêu cầu: ${req.method} ${req.url}`);
// Gọi middleware tiếp theo trong chuỗi
next();
}
Hàm next()
rất quan trọng. Nó báo hiệu cho Express.js rằng middleware hiện tại đã hoàn thành công việc và quyền điều khiển nên được chuyển cho hàm middleware tiếp theo. Nếu next()
không được gọi, yêu cầu sẽ bị treo và phản hồi sẽ không bao giờ được gửi đi.
Các loại Middleware
Express.js cung cấp một số loại middleware, mỗi loại phục vụ một mục đích riêng biệt:
- Middleware cấp ứng dụng (Application-level): Áp dụng cho tất cả các route hoặc các route cụ thể.
- Middleware cấp bộ định tuyến (Router-level): Áp dụng cho các route được định nghĩa trong một instance của router.
- Middleware xử lý lỗi (Error-handling): Được thiết kế đặc biệt để xử lý lỗi. Được đặt *sau* các định nghĩa route trong chuỗi middleware.
- Middleware tích hợp sẵn (Built-in): Đi kèm với Express.js (ví dụ:
express.static
để phục vụ các tệp tĩnh). - Middleware của bên thứ ba (Third-party): Được cài đặt từ các gói npm (ví dụ: body-parser, cookie-parser).
Các Mẫu Middleware Nâng cao
Hãy cùng khám phá một số mẫu nâng cao có thể cải thiện đáng kể chức năng, bảo mật và khả năng bảo trì của ứng dụng Express.js của bạn.
1. Middleware Xử lý Lỗi
Xử lý lỗi hiệu quả là điều tối quan trọng để xây dựng các ứng dụng đáng tin cậy. Express.js cung cấp một hàm middleware xử lý lỗi chuyên dụng, được đặt *cuối cùng* trong chuỗi middleware. Hàm này nhận bốn đối số: (err, req, res, next)
.
Đây là một ví dụ:
// Middleware xử lý lỗi
app.use((err, req, res, next) => {
console.error(err.stack); // Ghi lại lỗi để gỡ lỗi
res.status(500).send('Đã có lỗi xảy ra!'); // Phản hồi với mã trạng thái phù hợp
});
Những lưu ý chính khi xử lý lỗi:
- Ghi Log Lỗi: Sử dụng một thư viện ghi log (ví dụ: Winston, Bunyan) để ghi lại lỗi nhằm mục đích gỡ lỗi và giám sát. Cân nhắc ghi log các mức độ nghiêm trọng khác nhau (ví dụ:
error
,warn
,info
,debug
) - Mã Trạng thái: Trả về các mã trạng thái HTTP phù hợp (ví dụ: 400 cho Bad Request, 401 cho Unauthorized, 500 cho Internal Server Error) để thông báo bản chất của lỗi cho client.
- Thông báo Lỗi: Cung cấp các thông báo lỗi đầy đủ thông tin nhưng vẫn an toàn cho client. Tránh để lộ thông tin nhạy cảm trong phản hồi. Cân nhắc sử dụng một mã lỗi duy nhất để theo dõi các vấn đề nội bộ trong khi trả về một thông báo chung cho người dùng.
- Xử lý Lỗi Tập trung: Nhóm việc xử lý lỗi trong một hàm middleware chuyên dụng để tổ chức và bảo trì tốt hơn. Tạo các lớp lỗi tùy chỉnh cho các kịch bản lỗi khác nhau.
2. Middleware Xác thực và Phân quyền
Bảo mật API và bảo vệ dữ liệu nhạy cảm là rất quan trọng. Xác thực (Authentication) xác minh danh tính của người dùng, trong khi phân quyền (Authorization) xác định những gì người dùng được phép làm.
Các chiến lược xác thực:
- JSON Web Tokens (JWT): Một phương pháp xác thực không trạng thái (stateless) phổ biến, phù hợp cho các API. Máy chủ cấp một JWT cho client sau khi đăng nhập thành công. Client sau đó bao gồm token này trong các yêu cầu tiếp theo. Các thư viện như
jsonwebtoken
thường được sử dụng. - Sessions: Duy trì các phiên làm việc của người dùng bằng cách sử dụng cookie. Điều này phù hợp với các ứng dụng web nhưng có thể kém khả năng mở rộng hơn so với JWT. Các thư viện như
express-session
hỗ trợ quản lý session. - OAuth 2.0: Một tiêu chuẩn được áp dụng rộng rãi cho việc ủy quyền được ủy thác, cho phép người dùng cấp quyền truy cập vào tài nguyên của họ mà không cần chia sẻ trực tiếp thông tin đăng nhập. (ví dụ: đăng nhập bằng Google, Facebook, v.v.). Triển khai luồng OAuth bằng các thư viện như
passport.js
với các chiến lược OAuth cụ thể.
Các chiến lược phân quyền:
- Kiểm soát Truy cập Dựa trên Vai trò (RBAC): Gán vai trò (ví dụ: admin, editor, user) cho người dùng và cấp quyền dựa trên các vai trò này.
- Kiểm soát Truy cập Dựa trên Thuộc tính (ABAC): Một cách tiếp cận linh hoạt hơn, sử dụng các thuộc tính của người dùng, tài nguyên và môi trường để xác định quyền truy cập.
Ví dụ (Xác thực bằng JWT):
const jwt = require('jsonwebtoken');
const secretKey = 'YOUR_SECRET_KEY'; // Thay thế bằng một khóa mạnh, dựa trên biến môi trường
// Middleware để xác minh token JWT
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) return res.sendStatus(401); // Không được phép
jwt.verify(token, secretKey, (err, user) => {
if (err) return res.sendStatus(403); // Bị cấm
req.user = user; // Gắn dữ liệu người dùng vào yêu cầu
next();
});
}
// Ví dụ route được bảo vệ bằng xác thực
app.get('/profile', authenticateToken, (req, res) => {
res.json({ message: `Chào mừng, ${req.user.username}` });
});
Những lưu ý quan trọng về bảo mật:
- Lưu trữ An toàn Thông tin Đăng nhập: Không bao giờ lưu trữ mật khẩu dưới dạng văn bản thuần túy. Sử dụng các thuật toán băm mật khẩu mạnh như bcrypt hoặc Argon2.
- HTTPS: Luôn sử dụng HTTPS để mã hóa giao tiếp giữa client và máy chủ.
- Xác thực Đầu vào: Xác thực tất cả đầu vào của người dùng để ngăn chặn các lỗ hổng bảo mật như SQL injection và cross-site scripting (XSS).
- Kiểm tra Bảo mật Thường xuyên: Tiến hành kiểm tra bảo mật thường xuyên để xác định và giải quyết các lỗ hổng tiềm ẩn.
- Biến Môi trường: Lưu trữ thông tin nhạy cảm (khóa API, thông tin đăng nhập cơ sở dữ liệu, khóa bí mật) dưới dạng biến môi trường thay vì mã hóa cứng trong mã của bạn. Điều này giúp quản lý cấu hình dễ dàng hơn và thúc đẩy thực hành bảo mật tốt nhất.
3. Middleware Giới hạn Tốc độ (Rate Limiting)
Giới hạn tốc độ bảo vệ API của bạn khỏi việc lạm dụng, chẳng hạn như các cuộc tấn công từ chối dịch vụ (DoS) và tiêu thụ tài nguyên quá mức. Nó hạn chế số lượng yêu cầu mà một client có thể thực hiện trong một khoảng thời gian cụ thể.
Các thư viện như express-rate-limit
thường được sử dụng để giới hạn tốc độ. Cũng nên xem xét gói helmet
, nó bao gồm chức năng giới hạn tốc độ cơ bản cùng với một loạt các cải tiến bảo mật khác.
Ví dụ (Sử dụng express-rate-limit):
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 phút
max: 100, // Giới hạn mỗi IP 100 yêu cầu trong khoảng thời gian windowMs
message: 'Quá nhiều yêu cầu từ IP này, vui lòng thử lại sau 15 phút',
});
// Áp dụng giới hạn tốc độ cho các route cụ thể
app.use('/api/', limiter);
// Hoặc, áp dụng cho tất cả các route (thường không được khuyến khích trừ khi tất cả lưu lượng truy cập nên được xử lý như nhau)
// app.use(limiter);
Các tùy chọn tùy chỉnh cho việc giới hạn tốc độ bao gồm:
- Giới hạn tốc độ dựa trên địa chỉ IP: Cách tiếp cận phổ biến nhất.
- Giới hạn tốc độ dựa trên người dùng: Yêu cầu xác thực người dùng.
- Giới hạn tốc độ dựa trên phương thức yêu cầu: Giới hạn các phương thức HTTP cụ thể (ví dụ: yêu cầu POST).
- Lưu trữ tùy chỉnh: Lưu trữ thông tin giới hạn tốc độ trong cơ sở dữ liệu (ví dụ: Redis, MongoDB) để có khả năng mở rộng tốt hơn trên nhiều instance máy chủ.
4. Middleware Phân tích Nội dung Yêu cầu (Request Body)
Theo mặc định, Express.js không phân tích nội dung yêu cầu (request body). Bạn sẽ cần sử dụng middleware để xử lý các định dạng body khác nhau, chẳng hạn như JSON và dữ liệu được mã hóa URL. Mặc dù các triển khai cũ hơn có thể đã sử dụng các gói như `body-parser`, thực hành tốt nhất hiện nay là sử dụng middleware tích hợp sẵn của Express, có sẵn từ Express v4.16.
Ví dụ (Sử dụng middleware tích hợp sẵn):
app.use(express.json()); // Phân tích nội dung yêu cầu được mã hóa JSON
app.use(express.urlencoded({ extended: true })); // Phân tích nội dung yêu cầu được mã hóa URL
Middleware `express.json()` phân tích các yêu cầu đến có payload JSON và làm cho dữ liệu đã phân tích có sẵn trong `req.body`. Middleware `express.urlencoded()` phân tích các yêu cầu đến có payload được mã hóa URL. Tùy chọn `{ extended: true }` cho phép phân tích các đối tượng và mảng phức tạp.
5. Middleware Ghi Log
Ghi log hiệu quả là điều cần thiết để gỡ lỗi, giám sát và kiểm tra ứng dụng của bạn. Middleware có thể chặn các yêu cầu và phản hồi để ghi lại thông tin liên quan.
Ví dụ (Middleware Ghi Log Đơn giản):
const morgan = require('morgan'); // Một trình ghi log yêu cầu HTTP phổ biến
app.use(morgan('dev')); // Ghi log yêu cầu theo định dạng 'dev'
// Một ví dụ khác, định dạng tùy chỉnh
app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
});
Đối với môi trường sản xuất, hãy cân nhắc sử dụng một thư viện ghi log mạnh mẽ hơn (ví dụ: Winston, Bunyan) với các tính năng sau:
- Các Mức Ghi Log: Sử dụng các mức ghi log khác nhau (ví dụ:
debug
,info
,warn
,error
) để phân loại các thông báo log dựa trên mức độ nghiêm trọng của chúng. - Xoay Vòng Log: Triển khai xoay vòng log để quản lý kích thước tệp log và ngăn chặn các vấn đề về không gian đĩa.
- Ghi Log Tập trung: Gửi log đến một dịch vụ ghi log tập trung (ví dụ: ELK stack (Elasticsearch, Logstash, Kibana), Splunk) để dễ dàng giám sát và phân tích hơn.
6. Middleware Xác thực Yêu cầu
Xác thực các yêu cầu đến để đảm bảo tính toàn vẹn của dữ liệu và ngăn chặn hành vi không mong muốn. Điều này có thể bao gồm việc xác thực header yêu cầu, tham số truy vấn và dữ liệu nội dung yêu cầu.
Các thư viện để xác thực yêu cầu:
- Joi: Một thư viện xác thực mạnh mẽ và linh hoạt để định nghĩa schema và xác thực dữ liệu.
- Ajv: Một trình xác thực JSON Schema nhanh chóng.
- Express-validator: Một bộ middleware của express bao bọc validator.js để sử dụng dễ dàng với Express.
Ví dụ (Sử dụng Joi):
const Joi = require('joi');
const userSchema = Joi.object({
username: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required(),
});
function validateUser(req, res, next) {
const { error } = userSchema.validate(req.body, { abortEarly: false }); // Đặt abortEarly thành false để nhận tất cả lỗi
if (error) {
return res.status(400).json({ errors: error.details.map(err => err.message) }); // Trả về thông báo lỗi chi tiết
}
next();
}
app.post('/users', validateUser, (req, res) => {
// Dữ liệu người dùng hợp lệ, tiến hành tạo người dùng
res.status(201).json({ message: 'Người dùng được tạo thành công' });
});
Các thực hành tốt nhất để xác thực yêu cầu:
- Xác thực dựa trên Schema: Định nghĩa các schema để chỉ định cấu trúc và kiểu dữ liệu mong đợi của dữ liệu của bạn.
- Xử lý Lỗi: Trả về các thông báo lỗi đầy đủ thông tin cho client khi xác thực thất bại.
- Làm sạch Đầu vào: Làm sạch đầu vào của người dùng để ngăn chặn các lỗ hổng như cross-site scripting (XSS). Trong khi xác thực đầu vào tập trung vào *những gì* được chấp nhận, việc làm sạch tập trung vào *cách* đầu vào được biểu diễn để loại bỏ các yếu tố có hại.
- Xác thực Tập trung: Tạo các hàm middleware xác thực có thể tái sử dụng để tránh trùng lặp mã.
7. Middleware Nén Phản hồi
Cải thiện hiệu suất của ứng dụng bằng cách nén các phản hồi trước khi gửi chúng đến client. Điều này làm giảm lượng dữ liệu được truyền, dẫn đến thời gian tải nhanh hơn.
Ví dụ (Sử dụng middleware nén):
const compression = require('compression');
app.use(compression()); // Bật nén phản hồi (ví dụ: gzip)
Middleware compression
tự động nén các phản hồi bằng gzip hoặc deflate, dựa trên header Accept-Encoding
của client. Điều này đặc biệt có lợi cho việc phục vụ các tài sản tĩnh và các phản hồi JSON lớn.
8. Middleware CORS (Chia sẻ Tài nguyên Giữa các Nguồn gốc)
Nếu API hoặc ứng dụng web của bạn cần chấp nhận các yêu cầu từ các tên miền (nguồn gốc) khác nhau, bạn sẽ cần cấu hình CORS. Điều này liên quan đến việc thiết lập các header HTTP phù hợp để cho phép các yêu cầu từ các nguồn gốc khác nhau.
Ví dụ (Sử dụng middleware CORS):
const cors = require('cors');
const corsOptions = {
origin: 'https://your-allowed-domain.com',
methods: 'GET,POST,PUT,DELETE',
allowedHeaders: 'Content-Type,Authorization'
};
app.use(cors(corsOptions));
// HOẶC để cho phép tất cả các nguồn gốc (cho môi trường phát triển hoặc API nội bộ -- sử dụng cẩn thận!)
// app.use(cors());
Những lưu ý quan trọng về CORS:
- Nguồn gốc (Origin): Chỉ định các nguồn gốc (tên miền) được phép để ngăn chặn truy cập trái phép. Thường thì việc đưa các nguồn gốc cụ thể vào danh sách trắng sẽ an toàn hơn là cho phép tất cả các nguồn gốc (
*
). - Phương thức (Methods): Định nghĩa các phương thức HTTP được phép (ví dụ: GET, POST, PUT, DELETE).
- Headers: Chỉ định các header yêu cầu được phép.
- Yêu cầu Preflight: Đối với các yêu cầu phức tạp (ví dụ: với các header tùy chỉnh hoặc các phương thức khác ngoài GET, POST, HEAD), trình duyệt sẽ gửi một yêu cầu preflight (OPTIONS) để kiểm tra xem yêu cầu thực tế có được phép hay không. Máy chủ phải phản hồi với các header CORS phù hợp để yêu cầu preflight thành công.
9. Phục vụ Tệp Tĩnh
Express.js cung cấp middleware tích hợp sẵn để phục vụ các tệp tĩnh (ví dụ: HTML, CSS, JavaScript, hình ảnh). Điều này thường được sử dụng để phục vụ phần front-end của ứng dụng của bạn.
Ví dụ (Sử dụng express.static):
app.use(express.static('public')); // Phục vụ các tệp từ thư mục 'public'
Đặt các tài sản tĩnh của bạn vào thư mục public
(hoặc bất kỳ thư mục nào khác bạn chỉ định). Express.js sau đó sẽ tự động phục vụ các tệp này dựa trên đường dẫn tệp của chúng.
10. Middleware Tùy chỉnh cho các Tác vụ Cụ thể
Ngoài các mẫu đã thảo luận, bạn có thể tạo middleware tùy chỉnh phù hợp với nhu cầu cụ thể của ứng dụng. Điều này cho phép bạn đóng gói logic phức tạp và thúc đẩy khả năng tái sử dụng mã.
Ví dụ (Middleware tùy chỉnh cho Cờ Tính năng - Feature Flags):
// Middleware tùy chỉnh để bật/tắt các tính năng dựa trên một tệp cấu hình
const featureFlags = require('./config/feature-flags.json');
function featureFlagMiddleware(featureName) {
return (req, res, next) => {
if (featureFlags[featureName] === true) {
next(); // Tính năng được bật, tiếp tục
} else {
res.status(404).send('Tính năng không khả dụng'); // Tính năng bị tắt
}
};
}
// Ví dụ sử dụng
app.get('/new-feature', featureFlagMiddleware('newFeatureEnabled'), (req, res) => {
res.send('Đây là tính năng mới!');
});
Ví dụ này minh họa cách sử dụng một middleware tùy chỉnh để kiểm soát quyền truy cập vào các route cụ thể dựa trên cờ tính năng. Điều này cho phép các nhà phát triển kiểm soát việc phát hành tính năng mà không cần triển khai lại hoặc thay đổi mã chưa được kiểm duyệt đầy đủ, một thực tiễn phổ biến trong phát triển phần mềm.
Các Thực hành Tốt nhất và Lưu ý cho Ứng dụng Toàn cầu
- Hiệu suất: Tối ưu hóa middleware của bạn để đạt hiệu suất cao, đặc biệt là trong các ứng dụng có lưu lượng truy cập lớn. Giảm thiểu việc sử dụng các hoạt động tốn nhiều CPU. Cân nhắc sử dụng các chiến lược lưu vào bộ đệm (caching).
- Khả năng mở rộng: Thiết kế middleware của bạn để có thể mở rộng theo chiều ngang. Tránh lưu trữ dữ liệu phiên trong bộ nhớ; sử dụng bộ đệm phân tán như Redis hoặc Memcached.
- Bảo mật: Thực hiện các thực hành bảo mật tốt nhất, bao gồm xác thực đầu vào, xác thực, phân quyền và bảo vệ chống lại các lỗ hổng web phổ biến. Điều này rất quan trọng, đặc biệt là với tính chất quốc tế của đối tượng người dùng của bạn.
- Khả năng bảo trì: Viết mã sạch, có tài liệu tốt và theo mô-đun. Sử dụng các quy ước đặt tên rõ ràng và tuân theo một phong cách viết mã nhất quán. Mô-đun hóa middleware của bạn để tạo điều kiện bảo trì và cập nhật dễ dàng hơn.
- Khả năng kiểm thử: Viết các bài kiểm tra đơn vị và kiểm tra tích hợp cho middleware của bạn để đảm bảo nó hoạt động chính xác và để phát hiện các lỗi tiềm ẩn sớm. Kiểm tra middleware của bạn trong nhiều môi trường khác nhau.
- Quốc tế hóa (i18n) và Địa phương hóa (l10n): Cân nhắc quốc tế hóa và địa phương hóa nếu ứng dụng của bạn hỗ trợ nhiều ngôn ngữ hoặc khu vực. Cung cấp các thông báo lỗi, nội dung và định dạng được địa phương hóa để nâng cao trải nghiệm người dùng. Các framework như i18next có thể hỗ trợ các nỗ lực i18n.
- Múi giờ và Xử lý Ngày/Giờ: Lưu ý đến múi giờ và xử lý dữ liệu ngày/giờ cẩn thận, đặc biệt khi làm việc với đối tượng người dùng toàn cầu. Sử dụng các thư viện như Moment.js hoặc Luxon để thao tác ngày/giờ hoặc, tốt hơn là, sử dụng đối tượng Date tích hợp sẵn mới hơn của Javascript với khả năng nhận biết múi giờ. Lưu trữ ngày/giờ ở định dạng UTC trong cơ sở dữ liệu của bạn và chuyển đổi chúng sang múi giờ địa phương của người dùng khi hiển thị.
- Xử lý Tiền tệ: Nếu ứng dụng của bạn liên quan đến các giao dịch tài chính, hãy xử lý tiền tệ một cách chính xác. Sử dụng định dạng tiền tệ phù hợp và cân nhắc hỗ trợ nhiều loại tiền tệ. Đảm bảo dữ liệu của bạn được duy trì một cách nhất quán và chính xác.
- Tuân thủ Pháp lý và Quy định: Nhận thức được các yêu cầu pháp lý và quy định ở các quốc gia hoặc khu vực khác nhau (ví dụ: GDPR, CCPA). Thực hiện các biện pháp cần thiết để tuân thủ các quy định này.
- Khả năng Tiếp cận: Đảm bảo ứng dụng của bạn có thể tiếp cận được đối với người dùng khuyết tật. Tuân theo các hướng dẫn về khả năng tiếp cận như WCAG (Web Content Accessibility Guidelines).
- Giám sát và Cảnh báo: Thực hiện giám sát và cảnh báo toàn diện để phát hiện và ứng phó với các vấn đề một cách nhanh chóng. Giám sát hiệu suất máy chủ, lỗi ứng dụng và các mối đe dọa bảo mật.
Kết luận
Nắm vững các mẫu middleware nâng cao là rất quan trọng để xây dựng các ứng dụng Express.js mạnh mẽ, an toàn và có khả năng mở rộng. Bằng cách sử dụng các mẫu này một cách hiệu quả, bạn có thể tạo ra các ứng dụng không chỉ có chức năng mà còn dễ bảo trì và phù hợp với đối tượng người dùng toàn cầu. Hãy nhớ ưu tiên bảo mật, hiệu suất và khả năng bảo trì trong suốt quá trình phát triển của bạn. Với kế hoạch và triển khai cẩn thận, bạn có thể tận dụng sức mạnh của middleware Express.js để xây dựng các ứng dụng web thành công, đáp ứng nhu cầu của người dùng trên toàn thế giới.
Tài liệu đọc thêm: