Modules và Packages trong Node.js: CommonJS, ES Modules, npm và File System
- 23-07-2026
- Toanngo92
- 0 Comments
Module giúp chia một chương trình Node.js thành các phần nhỏ, có trách nhiệm rõ ràng và có thể tái sử dụng. Package bổ sung lớp quản lý ở cấp dự án: mã nguồn, metadata, phiên bản và dependency. Bài này đi từ ba nhóm module cơ bản đến CommonJS, ES Modules, npm, thao tác file bất đồng bộ và các utility module thường dùng.
Mục lục
1. Module và package khác nhau thế nào?
Module là một đơn vị mã có phạm vi riêng, có thể xuất ra hàm, class, object hoặc hằng số để tệp khác sử dụng. Package là một thư mục được mô tả bởi package.json; bên trong có thể chứa một hoặc nhiều module, tài nguyên, script và dependency.
| Khái niệm | Vai trò | Ví dụ |
|---|---|---|
| Module tích hợp | Có sẵn cùng Node.js | node:fs, node:path, node:os |
| Module cục bộ | Mã do dự án tự viết | ./math.js, ../config.js |
| Package bên thứ ba | Cài từ registry hoặc nguồn khác | express, zod |

Với module tích hợp, nên dùng tiền tố node:. Cách viết import { readFile } from 'node:fs/promises' cho biết rõ đây là API của Node.js và tránh nhầm với package trùng tên.
2. CommonJS: require và module.exports
CommonJS là hệ module truyền thống của Node.js. Tệp CommonJS dùng require() để nhập và module.exports hoặc exports để xuất giá trị.
// math.cjs
function sum(a, b) {
return a + b;
}
module.exports = { sum };
// app.cjs
const { sum } = require('./math.cjs');
console.log(sum(2, 3)); // 5
require('node:path'): nạp module tích hợp.require('./math.cjs'): nạp module cục bộ bằng đường dẫn tương đối.require('express'): nạp package từ câynode_modules.- Mỗi tệp có phạm vi riêng; biến ở cấp cao nhất không tự động trở thành biến toàn cục.
exports = valuekhông thay thế giá trị xuất của module. Khi muốn xuất một giá trị duy nhất, hãy dùngmodule.exports = value.
3. ES Modules: import và export
ES Modules (ESM) là chuẩn module của JavaScript. Node.js hỗ trợ đầy đủ cú pháp import/export. Có hai cách phổ biến để bật ESM: dùng phần mở rộng .mjs, hoặc đặt "type": "module" trong package.json để các tệp .js trong package được hiểu là ESM.
// package.json
{
"name": "module-demo",
"private": true,
"type": "module"
}
// math.js
export function sum(a, b) {
return a + b;
}
// app.js
import { sum } from './math.js';
console.log(sum(4, 6));
| CommonJS | ES Modules |
|---|---|
require() | import |
module.exports | export |
.cjs hoặc type: commonjs | .mjs hoặc type: module |
Có __dirname, __filename | Dùng import.meta.url khi cần vị trí module |
Trong dự án mới, hãy chọn một hệ module làm mặc định và khai báo type tường minh. Tránh trộn lẫn tùy tiện vì quy tắc phân giải, cache và cách truy cập tên tệp giữa CommonJS và ESM không hoàn toàn giống nhau.
4. Tạo package và quản lý dependency bằng npm
Khởi tạo package nhanh bằng npm init -y. Tệp package.json mô tả tên dự án, phiên bản, hệ module, script và các package mà ứng dụng cần.

npm init -y
npm install express
npm install --save-dev eslint
npm uninstall express
npm update
- dependencies: cần khi ứng dụng chạy ở môi trường production.
- devDependencies: chỉ phục vụ phát triển, kiểm thử hoặc build.
- package-lock.json: khóa cây dependency đã giải quyết để lần cài sau có kết quả ổn định hơn.
- Cài cục bộ: là lựa chọn mặc định cho thư viện của dự án.
- Công cụ CLI: có thể chạy bằng
npxhoặcnpm execthay vì cài toàn cục không cần thiết.
Không chỉnh sửa thủ công mã trong node_modules. Khi chia sẻ dự án, lưu package.json và thường cả package-lock.json vào Git; thư mục node_modules được tái tạo bằng npm install hoặc npm ci.
5. File System hiện đại với fs/promises
Module node:fs có dạng đồng bộ, callback và Promise. Trong luồng xử lý server, API Promise từ node:fs/promises thường dễ đọc hơn và không chặn event loop như các hàm *Sync.

import { appendFile, readFile, unlink, writeFile } from 'node:fs/promises';
const file = new URL('./notes.txt', import.meta.url);
try {
await writeFile(file, 'Modules trong Node.js\n', 'utf8');
await appendFile(file, 'CommonJS và ES Modules\n', 'utf8');
const content = await readFile(file, 'utf8');
console.log(content);
await unlink(file);
} catch (error) {
console.error('Không thể thao tác file:', error.message);
}
writeFile()mặc định ghi đè nội dung hiện có.appendFile()nối dữ liệu và tạo tệp nếu tệp chưa tồn tại.readFile(path, 'utf8')trả về chuỗi; nếu bỏ encoding, kết quả làBuffer.unlink()xóa tệp, vì vậy phải xác thực đường dẫn trước khi gọi.- Luôn
awaitthao tác trước khi sửa cùng một tệp lần tiếp theo để tránh tranh chấp dữ liệu.
6. Làm việc với đường dẫn bằng node:path
Không ghép đường dẫn bằng dấu / hoặc \ viết tay. Module node:path xử lý khác biệt giữa Windows và POSIX.
import path from 'node:path';
const report = path.join('data', 'reports', 'july.json');
console.log(report);
console.log(path.parse(report));
// { root, dir, base, ext, name }
console.log(path.extname(report)); // .json
console.log(path.dirname(report)); // data/reports (tùy hệ điều hành)
Khi đường dẫn đến từ người dùng, chỉ path.join() là chưa đủ để bảo mật. Cần chuẩn hóa, giới hạn trong thư mục cho phép và kiểm tra kết quả sau khi resolve để ngăn path traversal như ../../secret.txt.
7. Lấy thông tin hệ điều hành bằng node:os

import os from 'node:os';
console.log({
type: os.type(),
platform: os.platform(),
architecture: os.arch(),
cpuCount: os.cpus().length,
totalMemoryMB: Math.round(os.totalmem() / 1024 / 1024),
freeMemoryMB: Math.round(os.freemem() / 1024 / 1024),
});
Các giá trị này hữu ích cho log chẩn đoán và cấu hình thích ứng. Tuy nhiên, không nên dựa vào dung lượng RAM tức thời hoặc số CPU để tự động đưa ra quyết định quan trọng mà không đo tải thực tế.
8. Framework web và cách tổ chức ứng dụng
Framework web cung cấp router, middleware, xử lý request/response và quy ước tổ chức mã. Express là một lựa chọn phổ biến, nhưng bản thân Express không bắt buộc ứng dụng phải theo MVC. MVC, REST API hay full-stack là các cách tổ chức và phong cách kiến trúc; framework chỉ hỗ trợ triển khai chúng.

| Mô hình | Mục tiêu chính |
|---|---|
| MVC | Tách Model, View và Controller để giảm phụ thuộc giữa dữ liệu, giao diện và điều phối. |
| REST API | Cung cấp tài nguyên qua HTTP, dùng method và status code có ý nghĩa. |
| Full-stack | Kết hợp backend, frontend và công cụ xây dựng trong một hệ sinh thái. |
9. Bài thực hành: module quản lý hồ sơ
Ví dụ dưới đây tách thao tác dữ liệu thành module cục bộ, dùng ESM, fs/promises và path. Dữ liệu được lưu dưới dạng JSON để dễ quan sát.
// profile-store.js
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
const dataDir = path.resolve('data');
const dataFile = path.join(dataDir, 'profiles.json');
export async function saveProfiles(profiles) {
await mkdir(dataDir, { recursive: true });
await writeFile(dataFile, JSON.stringify(profiles, null, 2), 'utf8');
}
export async function loadProfiles() {
try {
return JSON.parse(await readFile(dataFile, 'utf8'));
} catch (error) {
if (error.code === 'ENOENT') return [];
throw error;
}
}
// app.js
import { loadProfiles, saveProfiles } from './profile-store.js';
const profiles = await loadProfiles();
profiles.push({ id: crypto.randomUUID(), name: 'An' });
await saveProfiles(profiles);
console.log(profiles);
Trong ứng dụng nhiều request đồng thời, cách đọc–sửa–ghi toàn bộ JSON có thể làm mất cập nhật. Khi dữ liệu lớn hoặc có ghi đồng thời, hãy dùng cơ sở dữ liệu, hàng đợi ghi hoặc cơ chế khóa phù hợp.
10. Checklist và câu hỏi tự kiểm tra
- Khai báo rõ
"type": "module"hoặc"type": "commonjs". - Dùng tiền tố
node:cho module tích hợp. - Cài thư viện của dự án cục bộ và phân biệt dependency với devDependency.
- Ưu tiên API bất đồng bộ trong code xử lý request.
- Dùng
pathhoặcURLthay vì nối chuỗi đường dẫn. - Bắt lỗi có chủ đích; không nuốt lỗi hoặc chỉ in lỗi rồi tiếp tục trong trạng thái không xác định.
- Điểm khác nhau cốt lõi giữa module và package là gì?
- Khi nào tệp
.jsđược Node.js hiểu là ES Module? - Vì sao không nên dùng
readFileSync()trong route xử lý nhiều request? dependenciesvàdevDependenciesphục vụ hai giai đoạn nào?- Cần kiểm tra gì trước khi xóa tệp có đường dẫn do người dùng cung cấp?
Tóm lại: module giúp tổ chức và tái sử dụng mã; package giúp đóng gói, phiên bản hóa và quản lý dependency. Nắm chắc CommonJS, ESM, npm, fs/promises, path và os là nền tảng để xây dựng ứng dụng Node.js có cấu trúc rõ ràng và an toàn hơn.

