
Chào các "coder nhí" Gen Z, hôm nay anh Creyt sẽ "bung lụa" một khái niệm nghe thì hàn lâm nhưng thực ra "dễ như ăn kẹo" trong Node.js, đó là module.exports. Nghe tên thôi đã thấy mùi "công xưởng" rồi đúng không? Đừng lo, anh sẽ biến nó thành câu chuyện "cửa hàng tạp hóa" cho các em dễ hình dung.
1. module.exports là gì mà "hot" thế? (Giải mã từ A-Z)
Này, các em cứ hình dung thế này: project của chúng ta không phải là một cái "nhà kho tổng" đổ tất cả mọi thứ vào một chỗ, hỗn độn như "cái chợ chiều" đâu. Mà nó phải là một "siêu thị" với các quầy hàng, gian hàng được sắp xếp gọn gàng, mỗi gian bán một loại mặt hàng riêng biệt.
Mỗi file JavaScript trong Node.js "mặc định" được coi là một "gian hàng" độc lập, một "module" riêng. Và module.exports chính là cái "menu" hoặc cái "bảng hiệu" mà gian hàng đó treo lên, để "khoe" với các gian hàng khác (hay các file khác) rằng: "Này, tao có món này ngon lắm, mày muốn dùng không?" Nó là cách bạn quyết định những gì sẽ được chia sẻ ra bên ngoài từ file hiện tại của bạn.
Tóm lại: module.exports là cơ chế của Node.js (theo chuẩn CommonJS) cho phép một module (file) "xuất" các giá trị (biến, hàm, đối tượng, class) để các module khác có thể "nhập" (import/require) và sử dụng lại. Nó giúp chúng ta:
- Phân chia code: Giúp project sạch sẽ, dễ đọc, dễ bảo trì hơn, tránh "spaghetti code" (code rối như mì ống).
- Tái sử dụng: Viết một lần, dùng nhiều nơi. "Đỡ phải copy-paste mỏi tay"!
- Độc lập: Mỗi module làm tốt một nhiệm vụ riêng, giảm thiểu sự phụ thuộc lẫn nhau.
2. "Thực đơn" code: Code Ví Dụ minh hoạ
Giờ thì chúng ta cùng xem cách "treo bảng hiệu" và "gọi món" nhé.
Ví dụ 1: Xuất một "món" duy nhất (một hàm)
Giả sử bạn có một file math.js chuyên làm nhiệm vụ tính toán.
// math.js
function add(a, b) {
return a + b;
}
// "Treo bảng hiệu" món "add" ra ngoài
module.exports = add;
Và giờ, trong file app.js, bạn muốn "gọi món" add này:
// app.js
// "Gọi món" từ gian hàng "./math.js"
const congHaiSo = require('./math');
console.log(congHaiSo(10, 5)); // Output: 15
Đơn giản đúng không? module.exports = add; có nghĩa là, khi ai đó require('./math'), họ sẽ nhận được chính cái hàm add đó.
Ví dụ 2: Xuất một "combo" nhiều món (một đối tượng)
Thông thường, một module sẽ có nhiều thứ muốn chia sẻ. Lúc này, chúng ta sẽ "đóng gói" chúng vào một đối tượng (object).
// calculator.js
const PI = 3.14159;
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
// "Treo bảng hiệu" một combo các món
module.exports = {
PI: PI,
add: add,
tru: subtract, // Đổi tên cho dễ gọi ở ngoài
nhan: (a, b) => a * b // Xuất luôn hàm mũi tên
};
Và đây là cách bạn "gọi combo" trong main.js:
// main.js
const myCalculator = require('./calculator');
console.log(myCalculator.PI); // Output: 3.14159
console.log(myCalculator.add(10, 5)); // Output: 15
console.log(myCalculator.tru(10, 5)); // Output: 5
console.log(myCalculator.nhan(4, 2)); // Output: 8
Ví dụ 3: "Exports" "tưởng bở" và module.exports "thật sự"
Đây là cái bẫy mà nhiều bạn mới học Node.js hay mắc phải. Trong Node.js, có một biến exports cũng được dùng để xuất. Thực ra, exports chỉ là một tham chiếu (một cái tên gọi khác) đến module.exports ban đầu (là một đối tượng rỗng {}).
- Nếu bạn gán thuộc tính cho
exports(ví dụ:exports.tenMon = giaTri;), bạn đang thêm thuộc tính vào đối tượng màmodule.exportsđang trỏ tới. Cách này hoạt động. - Nhưng nếu bạn gán thẳng cho
exports(ví dụ:exports = { ... };), bạn đang làm choexportstrỏ tới một đối tượng hoàn toàn mới, và lúc nàymodule.exportsvẫn trỏ tới đối tượng rỗng ban đầu. Cách này sẽ không hoạt động như bạn mong đợi.
Luôn nhớ: module.exports là cái "chốt hạ" cuối cùng quyết định cái gì sẽ được xuất ra. Anh em cứ dùng module.exports là "ăn chắc mặc bền" nhất.
// trickyModule.js
// Cách này OK: Thêm thuộc tính vào đối tượng mà module.exports đang trỏ tới
exports.greeting = 'Hello from exports!';
exports.sayHi = () => 'Hi there!';
// CÁCH NÀY KHÔNG OK: exports bị gán lại, nhưng module.exports thì không!
// exports = { name: 'Creyt' }; // Dòng này sẽ làm mất hiệu lực của greeting và sayHi khi require
// CÁCH NÀY LUÔN OK: Gán trực tiếp cho module.exports
// module.exports = { name: 'Creyt', age: 30 }; // Nếu dùng dòng này, greeting và sayHi sẽ bị ghi đè
Khi require('./trickyModule'):
- Nếu chỉ dùng
exports.greetingvàexports.sayHi, bạn sẽ nhận được{ greeting: 'Hello from exports!', sayHi: [Function] }. - Nếu bạn thêm dòng
exports = { name: 'Creyt' };và không cómodule.exports = ...nào khác, bạn sẽ nhận được{ greeting: 'Hello from exports!', sayHi: [Function] }. Dòngexports = { name: 'Creyt' };bị bỏ qua! - Nếu bạn thêm dòng
module.exports = { name: 'Creyt', age: 30 };, bạn sẽ nhận được{ name: 'Creyt', age: 30 }. Mọi thứ bạn gán choexportstrước đó đều bị ghi đè.
Lời khuyên từ Creyt: Để tránh nhầm lẫn, hãy luôn dùng module.exports = ... khi bạn muốn xuất một giá trị duy nhất hoặc một đối tượng tổng hợp các giá trị. Coi exports như một biến "tạm" thôi.

3. Mẹo vặt "hack não" và Best Practices từ Creyt
- "Đồng phục" code: Hãy thống nhất cách bạn xuất code. Hoặc luôn dùng
module.exports = { ... }cho đối tượng, hoặc luôn dùngexports.tenMon = ...cho từng món lẻ. Đừng "nửa nạc nửa mỡ" mà loạn. - "Kín cổng cao tường": Chỉ xuất những gì "cần thiết" để các module khác sử dụng. Những hàm, biến "nội bộ" chỉ phục vụ cho module của bạn thì cứ để private, đừng "khoe" ra làm gì. Như vậy mới "bảo mật" và dễ quản lý.
- Tên gọi "sang chảnh": Đặt tên cho các hàm, biến bạn xuất sao cho rõ ràng, dễ hiểu. "Đừng đặt tên kiểu 'func1', 'dataA' nhé, nghe 'phèn' lắm!" (Đừng dùng tiếng Anh kiểu 'function1', 'dataA' nhé, nghe 'phèn' lắm!)
- Tương lai gọi tên ES Modules: Hiện tại Node.js vẫn dùng
module.exports(CommonJS) là chính. Nhưng tương lai là của ES Modules (import/export). Cứ học vững cái này đã, cái kia từ từ "chiến" sau.
4. "Ứng dụng thực tế" - Ai đang dùng module.exports?
"Hỏi ngược lại thì đúng hơn: ai không dùng mới là lạ!" Bất kỳ dự án Node.js nào, từ nhỏ đến lớn, đều sử dụng module.exports (hoặc ES Modules) để cấu trúc code.
- Express.js: Khi bạn định nghĩa các route, middleware, controller, service, model... trong một ứng dụng Express, bạn đều dùng
module.exportsđể "xuất" chúng ra và "nhập" vào fileapp.jschính. - Các thư viện NPM: Hầu hết các thư viện bạn cài từ npm (như
lodash,axios,moment...) đều được xây dựng dựa trên cơ chế module này để bạn có thểrequirevà sử dụng chúng. - Microservices: Trong kiến trúc microservices, mỗi service là một "gian hàng" độc lập, "xuất" ra các API của nó để các service khác có thể "gọi" đến.
5. Thử nghiệm và Nên dùng cho case nào?
Anh Creyt đã "thử" qua đủ loại cách rồi, và đây là lời khuyên chân thành:
-
Dùng
module.exports = một_giá_trị_duy_nhất;khi: Module của bạn chỉ có một nhiệm vụ chính yếu và bạn muốn xuất thẳng cái nhiệm vụ đó (ví dụ: một hàm tiện ích, một class duy nhất). Rất gọn gàng và rõ ràng.- Ví dụ:
module.exports = new DatabaseConnection();hoặcmodule.exports = authenticateUser;
- Ví dụ:
-
Dùng
module.exports = { key1: value1, key2: value2, ... };khi: Bạn muốn xuất nhiều thứ từ một module. Đây là cách phổ biến nhất và được khuyến khích vì nó rõ ràng và linh hoạt. Nó giúp bạn tổ chức các "món hàng" của mình thành một "combo" có tên.- Ví dụ:
module.exports = { connectDB, getUser, createUser };
- Ví dụ:
-
Tránh dùng
exports = { ... };: Như đã giải thích ở mục 2, nó sẽ không hoạt động như bạn nghĩ và dễ gây nhầm lẫn. Hãy luôn nhớmodule.exportslà "ông chủ" cuối cùng.
Vậy đó, module.exports không phải là cái gì quá "hack não" đúng không? Nó chỉ là cách chúng ta tổ chức code cho "ngon lành cành đào" hơn thôi. Hãy thực hành nhiều vào để "master" nó nhé các Gen Z tương lai của làng công nghệ!
Thuộc Series: Nodejs
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!