Node.js: Sistema de Módulos
Última atualização: 2026-08-26
O projeto de Charlie havia crescido desmedidamente, passando de 3 arquivos para 30. Todas as funções, configurações e classes utilitárias estavam amontoadas em um único e enorme app.js, e alterar uma única function exigia meia hora de busca por entre 2.000 linhas de código. Ele decidiu dividir o código em módulos separados, mas descobriu que require e import pareciam diferentes, module.exports e exports estavam sempre se confundindo, e dependências circulares estavam fazendo com que o programa gerasse um monte de undefined. Nesta lição, vamos acompanhar Charlie enquanto ele desvenda o sistema de módulos do Node.js, transformando seu código de uma confusão emaranhada em um conjunto bem organizado de blocos de construção.
Você aprenderá:
- Organizar o código usando CommonJS
require/module.exports/exports - Configurações
import,exporte"type":"module"utilizando módulos ES - Compreender o mecanismo de busca de módulos do
require(integrado → node_modules → caminho) - Compreender o mecanismo de armazenamento em cache do módulo e a função do
require.cache - Identificar problemas de dependência circular e compreender como o Node.js lida com eles
1. Módulos CommonJS
(1) Exportação com module.exports
O Node.js utiliza a especificação de módulos CommonJS por padrão. Cada arquivo é um módulo que exporta valores usando module.exports, e outros arquivos os carregam usando require().
// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
(2) Carregando módulos com require
require() Aceita um identificador de módulo e retorna o valor module.exports correspondente a esse módulo.
// app.js
const math = require('./math');
console.log(math.add(10, 3)); // 13
console.log(math.subtract(10, 3)); // 7
(3) O atalho exports
exports é uma referência a module.exports e é adequado para adicionar propriedades uma a uma.
// logger.js
exports.info = function (msg) {
console.log(`[INFO] ${msg}`);
};
exports.error = function (msg) {
console.log(`[ERROR] ${msg}`);
};
▶ Exemplo: Exportação de uma única função versus exportação de um objeto
// greet.js — Export a Single Function
module.exports = function (name) {
return `Hello, ${name}!`;
};
// config.js — Export Objects
module.exports = {
port: 3000,
host: 'localhost',
debug: true,
};
// app.js
const greet = require('./greet');
const config = require('./config');
console.log(greet('Charlie')); // Hello, Charlie!
console.log(`Server: ${config.host}:${config.port}`); // Server: localhost:3000
(4) Diferenças entre module.exports e exports
| Recurso | module.exports | exports |
|---|---|---|
| Essência | O objeto efetivamente exportado pelo módulo | Referência a module.exports |
| Exportação de tarefa | ✅ module.exports = fn |
❌ exports = fn Remover referência |
| Adicionar um por um | ✅ module.exports.foo = fn |
✅ exports.foo = fn |
| Exportar um único valor | ✅ Recomendado | ❌ Indisponível |
| Segurança | Sempre válido | Vence após a reatribuição |
Princípio fundamental: Se você precisar exportar uma única função, classe ou um objeto totalmente novo, deve usar
module.exports;exportssó pode ser usado para adicionar propriedades.
2. Módulos ES
(1) Sintaxe básica
Os Módulos ES (ESM) são o padrão oficial de módulos JavaScript, utilizando a sintaxe export e import.
// utils.mjs
export function square(n) {
return n * n;
}
export const VERSION = '2.0.0';
export default function greet(name) {
return `Hello, ${name}!`;
}
// app.mjs
import greet, { square, VERSION } from './utils.mjs';
console.log(greet('Charlie')); // Hello, Charlie!
console.log(square(5)); // 25
console.log(VERSION); // 2.0.0
(2) Três maneiras de ativar o ESM
| Método | Descrição |
|---|---|
Extensão de arquivo .mjs |
O Node.js a processa automaticamente como ESM |
"type": "module" no arquivo package.json |
Os arquivos com o nome .js no projeto são configurados por padrão para ESM |
--input-type=module |
Argumento da linha de comando usado para entrada stdin |
// package.json
{
"type": "module"
}
▶ Exemplo:(3) Exportações nomeadas e exportações padrão
// shapes.mjs
export const PI = 3.14159;
export function circleArea(radius) {
return PI * radius * radius;
}
export default class Shape {
constructor(name) {
this.name = name;
}
describe() {
return `This is a ${this.name}`;
}
}
▶ Exemplo: Exportação e reexportação unificadas
// api.mjs — Batch Export
export { addUser, removeUser } from './users.mjs';
export { logError } from './logger.mjs';
// You can also rename it
export { add as addUser } from './math.mjs';
3. Comparação entre CommonJS e ESM
(1) Principais diferenças
| Dimensão | CommonJS | Módulos ES |
|---|---|---|
| Sintaxe | require() / module.exports |
import / export |
| Método de carregamento | Síncrono, carregamento em tempo de execução | Assíncrono, análise estática em tempo de compilação |
| Tipo de valor | Cópia de valor (tipos primitivos) | Vinculação de valor (referência ativa) |
| Nível superior | module.exports |
undefined |
| Dependências circulares | Retorna exportações não resolvidas | Ligação de referência, mas pode estar na TDZ |
| Caso de uso | Projeto Node.js (padrão) | Novo projeto, compartilhamento de código no navegador |
| Extensão do arquivo | .js / .cjs |
.mjs / .js (tipo: módulo) |
▶ Exemplo:(2) Cópia de valor versus vinculação
// counter.cjs — CommonJS
let count = 0;
function increment() {
count++;
}
module.exports = { count, increment };
// counter.mjs — ESM
export let count = 0;
export function increment() {
count++;
}
// CJS: count is a copy, it won't change
const c = require('./counter.cjs');
c.increment();
console.log(c.count); // 0 (still the initial value)
// ESM: count is bound, real-time updates
import { count, increment } from './counter.mjs';
increment();
console.log(count); // 1 (updated)
▶ Exemplo: Importação de um módulo CJS no ESM
// legacy.cjs
module.exports = { legacyMethod() { return 'old school'; } };
// app.mjs
import cjs from './legacy.cjs';
console.log(cjs.legacyMethod()); // old school
Quando
importé um módulo CJS no ESM, o valor demodule.exportsé usado como exportação padrão.
4. O mecanismo de pesquisa do módulo require
(1) Processo de busca
Quando você digita require('express'), o Node.js realiza a busca na seguinte order:
flowchart TD
A["require('express')"] --> B{Does it have a built-in module??}
B -- Yes --> C[Return built-in module]
B -- No --> D{Path starting with ./ or / ?}
D -- Yes --> E[Find file by path]
E --> E1[Try .js / .json / .node]
E1 --> E2[Try index.js]
D -- No --> F[Search node_modules]
F --> F1[Current Directory/node_modules/express]
F1 --> F2[Parent directory/node_modules/express]
F2 --> F3[Move up one level at a time until root directory]
F3 --> F4{Found?}
F4 -- No --> G[Throw MODULE_NOT_FOUND]
F4 -- Yes --> H[Load and cache module]
E2 --> H
C --> H
(2) Regras de resolução de caminho
| Parâmetro obrigatório | Método de análise | Exemplo |
|---|---|---|
./math |
Caminho relativo ao arquivo atual | ./math → /project/src/math.js |
../utils |
Em relação ao diretório pai | ../utils → /project/utils.js |
/abs/path |
Caminho absoluto | /lib/helper.js |
express |
Módulos integrados → node_modules | Pesquisar por nível |
| Pacote Scope |
▶ Exemplo: Visualizando o caminho de resolução do módulo
// show-paths.js
console.log(module.paths);
[
'/project/src/node_modules',
'/project/node_modules',
'/node_modules',
'C:\\Users\\Charlie\\.node_modules',
'C:\\Users\\Charlie\\.node_libraries',
'C:\\Program Files\\nodejs\\lib\\node'
]
5. Mecanismo de armazenamento em cache de módulos
(1) Como funciona o armazenamento em cache
require Quando um módulo é carregado pela primeira vez, seu código é executado e o resultado é armazenado em cache. Posteriormente, require o mesmo módulo retorna diretamente o resultado armazenado em cache, sem reexecutar o código.
// counter.js
console.log('counter.js executed!');
let count = 0;
module.exports = {
increment() { return ++count; },
getCount() { return count; },
};
// app.js
const c1 = require('./counter'); // counter.js executed!
const c2 = require('./counter'); // (no output, using cache)
console.log(c1 === c2); // true
console.log(c1.increment()); // 1
console.log(c2.getCount()); // 1 (shared state)
(2) require.cache
Todos os módulos carregados são armazenados em cache no objeto require.cache, tendo o caminho absoluto do módulo como chave.
// inspect-cache.js
const path = require('path');
const math = require('./math');
const cacheKey = path.resolve(__dirname, 'math.js');
console.log(require.cache[cacheKey] !== undefined); // true
console.log(require.cache[cacheKey].exports === math); // true
▶ Exemplo: Limpar o cache para implementar a recarga dinâmica
// hot-reload.js
function loadConfig() {
const path = require('path');
const cacheKey = path.resolve(__dirname, 'config.js');
delete require.cache[cacheKey];
return require('./config');
}
const cfg1 = loadConfig();
// ... config.js Modified ...
const cfg2 = loadConfig(); // Re-execute, loading the latest content
Se você excluir a entrada em
require.cachee, em seguida, executarrequirenovamente, o Node.js reexecutará esse módulo. Isso é útil para a recarga dinâmica em um ambiente de desenvolvimento, mas use com cautela em um ambiente de produção.
6. Visão geral dos módulos integrados
(1) Referência rápida para módulos integrados comuns
O Node.js vem com um grande número de módulos integrados, prontos para uso sem necessidade de instalação.
| Módulo | Finalidade | Métodos/propriedades comuns |
|---|---|---|
fs |
Operações no sistema de arquivos | readFile, writeFile, readdir, stat |
path Processamento de trajetória join, resolve, parse, extname, basename |
||
http |
Servidor/Cliente HTTP | createServer, get, request |
https |
Servidor/Cliente HTTPS | createServer, get, request |
url Análise e construção de URLs URL, fileURLToPath, pathToFileURL |
||
os |
Informações sobre o sistema operacional | cpus, freemem, hostname, platform |
events |
Emissor de eventos | EventEmitter, on, emit, off |
stream |
Processamento de fluxo | Readable, Writable, Transform, pipe |
crypto |
Criptografia e Hash | createHash, createHmac, randomBytes |
util |
Utilitário | promisify, callbackify, format, inspect |
child_process |
Gerenciamento de processos filhos | exec, spawn, fork |
buffer |
Processamento de dados binários | Buffer.alloc, Buffer.from, concat |
▶ Exemplo:(2) Os módulos integrados não requerem instalação
const fs = require('fs');
const path = require('path');
const os = require('os');
console.log(os.platform()); // win32 / darwin / linux
console.log(path.join('/project', 'src', 'app.js')); // /project/src/app.js
▶ Exemplo: Introdução rápida com path e os
const path = require('path');
const os = require('os');
const filePath = '/project/src/utils/helper.js';
console.log(path.extname(filePath)); // .js
console.log(path.dirname(filePath)); // /project/src/utils
console.log(path.basename(filePath)); // helper.js
console.log(`CPU cores: ${os.cpus().length}`);
console.log(`Free memory: ${(os.freemem() / 1024 / 1024).toFixed(0)} MB`);
7. Dependências circulares
(1) O que é uma dependência circular?
O Módulo A depende do Módulo B, e o Módulo B depende do Módulo A, criando uma referência circular. O Node.js não entra em um loop infinito; em vez disso, ele retorna as exports da parte que já foi executada.
▶ Exemplo:(2) Como o Node.js lida com isso
// a.js
exports.loaded = false;
const b = require('./b');
exports.loaded = true;
console.log('a.js - b.loaded =', b.loaded);
// b.js
exports.loaded = false;
const a = require('./a'); // Received a Unfinished exports { loaded: false }
exports.loaded = true;
console.log('b.js - a.loaded =', a.loaded);
node a.js
b.js - a.loaded = false
a.js - b.loaded = true
Quando b.js executa require('./a'), a.js ainda não terminou de ser executado; portanto, o Node.js retorna a parte de a.js à qual já foi atribuído um valor naquele momento ({ loaded: false }).
(3) Estratégias para evitar dependências circulares
| Estratégia | Descrição |
|---|---|
| Extrair a lógica compartilhada | Mover as partes comuns para um terceiro módulo |
| Requisito diferido | Mover require para dentro da função, de modo que seja carregado somente quando chamado |
| Desacoplamento de eventos | Use o EventEmitter em vez de chamadas diretas |
| Injeção de dependências | Passar dependências por meio de parâmetros, em vez de codificá-las diretamente |
▶ Exemplo: Adiar require para resolver dependências circulares
// user.js
exports.getName = function () {
return 'Charlie';
};
exports.getProfile = function () {
const format = require('./format'); // Delay until the time of the call require
return format.upper(exports.getName());
};
// format.js
exports.upper = function (str) {
return str.toUpperCase();
};
exports.getUserDisplay = function () {
const user = require('./user'); // Deferred require
return `User: ${user.getName()}`;
};
O adiamento
requiregarante que o módulo carregue suas dependências somente quando um método for chamado pela primeira vez, momento em que ambos os módulos já terão sido totalmente inicializados, evitando assim a recuperação deexportsincompletos.
8. Exemplo abrangente: Projeto modular
A seguir, vamos criar um projeto modular que inclui um módulo de utilitários, um módulo de registro de eventos e um ponto de entrada principal:
// math.js — Tools Module
const PI = 3.14159;
function circleArea(radius) {
return PI * radius * radius;
}
function rectangleArea(width, height) {
return width * height;
}
function round(value, decimals = 2) {
const factor = Math.pow(10, decimals);
return Math.round(value * factor) / factor;
}
module.exports = { circleArea, rectangleArea, round };
// logger.js — Log Module
const LEVELS = { INFO: 'INFO', WARN: 'WARN', ERROR: 'ERROR' };
function formatMessage(level, msg) {
const timestamp = new Date().toISOString();
return `[${timestamp}] [${level}] ${msg}`;
}
function info(msg) {
console.log(formatMessage(LEVELS.INFO, msg));
}
function warn(msg) {
console.warn(formatMessage(LEVELS.WARN, msg));
}
function error(msg) {
console.error(formatMessage(LEVELS.ERROR, msg));
}
module.exports = { info, warn, error, LEVELS };
// app.js — Main Entrance
const { circleArea, rectangleArea, round } = require('./math');
const { info, error } = require('./logger');
const radius = 5;
const area = round(circleArea(radius));
info(`Circle area (r=${radius}): ${area}`);
const roomArea = rectangleArea(4.5, 6.2);
info(`Room area: ${round(roomArea)} sqm`);
if (radius < 0) {
error('Radius cannot be negative');
} else {
info('Calculation complete');
}
node app.js
[2026-07-03T10:30:00.000Z] [INFO] Circle area (r=5): 78.54
[2026-07-03T10:30:00.001Z] [INFO] Room area: 27.9 sqm
[2026-07-03T10:30:00.001Z] [INFO] Calculation complete
❓ Perguntas Frequentes
P: É possível usar CommonJS e ESM juntos? R: Existem limitações. No ESM, é possível importar módulos CJS (com
module.exportscomo exportação padrão), mas no CJS não é possível usarrequirepara carregar módulos ESM; em vez disso, é necessário usar a função dinâmicaimport(). Recomenda-se que os projetos sigam uma única especificação de módulo.
P:
requireé síncrono ou assíncrono? R: Síncrono.requirebloqueia a execução do código até que o módulo termine de carregar. É por isso que o Node.js recomenda colocarrequireno início de um arquivo e evitar chamadas frequentes derequirepara novos módulos no caminho de acesso frequente durante a execução.
P: Qual é a diferença entre
module.exportseexports? R:exportsé uma referência abreviada paramodule.exports. É possível adicionar propriedades usandoexports.xxx = ..., masexports = xxxquebra a referência, fazendo com que a exportação falhe. Quando for necessário exportar uma única função ou um objeto totalmente novo, é preciso usarmodule.exports = xxx.
P: Como faço para visualizar o cache de um módulo? R: Você pode visualizá-lo usando o objeto
require.cache. As chaves são os caminhos absolutos do módulo, e os valores são os objetos do módulo. Se você excluir uma chave (delete require.cache[key]), o módulo será reexecutado na próxima vez que você usarrequire.
P: O que é uma dependência circular? Como o Node.js lida com isso? R: Uma dependência circular ocorre quando dois ou mais módulos dependem uns dos outros. O Node.js não fica preso em um loop infinito; em vez disso, ele retorna os
exportsque ainda não foram totalmente executados no ponto em que o loop começa (que podem ser objetos incompletos), o que pode resultar em propriedades indefinidas. As soluções incluem extrair um módulo comum, adiar chamadasrequiree desacoplar eventos.
P: Por que as instruções
importdevem ser escritas no nível superior no ESM? R: O ESM é analisado estaticamente; portanto, as dependências são determinadas durante a fase de compilação, o que facilita o “tree shaking” e a otimização. Para cenários de carregamento dinâmico, você pode usar a funçãoimport().
P: O que acontece quando você usa
requirepara carregar um arquivo JSON? R: O Node.js lê o arquivo JSON e o analisa automaticamente usandoJSON.parse(), retornando um objeto JavaScript analisado. Isso é comumente usado para carregar arquivos de configuração.
📖 Resumo
- O CommonJS é a especificação padrão de módulos para o Node.js; use
requirepara carregar emodule.exportspara exportar - Os módulos ES são um padrão oficial do JavaScript; use
import/exporte habilite-os com o sufixo.mjsou"type":"module" requireOrdem de pesquisa: Módulos integrados → Caminhos relativos/absolutos → node_modules, avançando de nível para nível, de baixo para cima- Após o módulo ser carregado pela primeira vez, ele é armazenado em cache em
require.cache; as chamadas subsequentes derequireretornam diretamente a versão armazenada em cache. exportsé uma referência amodule.exports; reatribuir esse valor quebrará a referência- Quando ocorre uma dependência circular, o Node.js retorna
exportsnão resolvido; isso pode ser evitado por meio de estratégias como o adiamento das chamadasrequire. - Os módulos integrados (como fs, path, http, os, etc.) não precisam ser instalados; basta usar
requirepara utilizá-los.
📝 Exercícios
- Crie o módulo
calculator.js, exporte as quatro funçõesadd,subtract,multiplyedividee, em seguida, importe-as e utilize-as nomain.js - Converta a pergunta anterior para uma versão ESM: use a sintaxe
exporte o sufixo.mjs, e carregue-a usandoimport. - Escreva um código para verificar a existência de
require.cache: após carregar um módulo, exiba as informações sobre esse módulo a partir derequire.cache. - Crie intencionalmente uma dependência circular (a.js depende de b.js e b.js depende de a.js), observe o resultado e, em seguida, corrija o problema usando chamadas de dependência diferidas.
- Use os módulos
patheospara exibir a plataforma atual do sistema operacional, o número de núcleos da CPU e o caminho absoluto do diretório atual.