CommonJS no Node.js é o sistema de módulos tradicional do runtime. Ele usa require() para carregar dependências e module.exports ou exports para disponibilizar valores a outros arquivos.
Mesmo com a adoção crescente de ECMAScript Modules, CommonJS continua presente em aplicações antigas, ferramentas, scripts e milhares de pacotes. Entender seu funcionamento é essencial para manter sistemas existentes, diagnosticar ciclos de dependência e planejar migrações seguras.
Neste guia, você aprenderá resolução de módulos, cache, exports, caminhos, interoperabilidade com ESM, testes, segurança e boas práticas para produção.
Como funciona o require
O require() recebe um identificador e retorna o valor exportado pelo módulo:
const fs = require('node:fs');
const { somar } = require('./math');
console.log(somar(20, 22));O carregamento é síncrono. Por isso, ele funciona bem para módulos locais e dependências instaladas durante a inicialização, mas não deve ser usado para arquivos remotos ou tarefas lentas.
Criando um módulo
// math.js
function somar(a, b) {
return a + b;
}
module.exports = { somar };O consumidor recebe o objeto exportado:
const { somar } = require('./math');module.exports e exports
exports começa como uma referência para module.exports. Adicionar propriedades funciona:
exports.buscar = async function buscar(id) {
return { id };
};Reatribuir exports não altera o valor exportado:
exports = function criar() {}; // não funciona como esperadoPara exportar uma função, classe ou objeto único, use:
module.exports = function criarAplicacao() {
return { iniciar() {} };
};Wrapper do módulo
O Node.js envolve cada arquivo CommonJS em uma função. Isso fornece variáveis locais como module, exports, require, __filename e __dirname.
console.log(__filename);
console.log(__dirname);Essas variáveis facilitam caminhos relativos ao arquivo atual, mas não existem da mesma forma em ESM.
Resolução de módulos
O identificador pode apontar para módulo interno, arquivo relativo, diretório, pacote instalado ou subpath público. Prefira caminhos explícitos e APIs públicas. Importar arquivos internos de uma dependência cria acoplamento e pode quebrar em atualizações.
Cache do require
Após o primeiro carregamento, o módulo é armazenado em require.cache. Chamadas seguintes normalmente retornam a mesma instância:
const a = require('./config');
const b = require('./config');
console.log(a === b); // trueIsso significa que estado mutável exportado é compartilhado. Evite usar módulos como depósitos globais de dados sem ciclo de vida claro.
Efeitos colaterais no carregamento
O código no topo do arquivo executa durante o primeiro require(). Evite conectar banco, abrir porta ou iniciar timers durante o import.
module.exports = function criarRepositorio(client) {
return {
buscar(id) {
return client.query('SELECT * FROM users WHERE id = $1', [id]);
}
};
};Fábricas tornam testes, inicialização e shutdown mais previsíveis.
JSON com require
const config = require('./config.json');O arquivo é lido e armazenado em cache. Para configuração dinâmica ou validação rigorosa, leia explicitamente e valide com schema. Veja JSON Schema no Node.js.
Carregamento condicional
let adapter;
if (process.env.STORAGE === 'memory') {
adapter = require('./memory-storage');
} else {
adapter = require('./postgres-storage');
}Use com moderação. Dependências ocultas dificultam análise, testes e empacotamento.
require.resolve
require.resolve() retorna o caminho que seria carregado:
const caminho = require.resolve('meu-pacote');
console.log(caminho);É útil para diagnóstico e ferramentas, mas não deve ser usado para acessar arquivos privados de pacotes.
Ciclos de dependência
Se A requer B e B requer A, um deles pode receber um objeto parcialmente inicializado. O problema costuma aparecer como propriedades indefinidas ou comportamento dependente da ordem.
Extraia interfaces, constantes e utilitários para um terceiro módulo. Outra alternativa é passar dependências por parâmetro em vez de importá-las diretamente.
Interoperabilidade com ESM
Um módulo CommonJS pode usar import dinâmico:
async function executar() {
const { processar } = await import('./processador.mjs');
return processar();
}
module.exports = { executar };O resultado de import() é uma Promise. Consulte Dynamic Import no Node.js para estratégias de lazy loading.
Importando CommonJS a partir de ESM
import pacote from './legacy.cjs';O valor de module.exports normalmente aparece como export default. Não presuma que exports nomeados sintetizados estarão disponíveis em todos os pacotes.
Estrutura de aplicação
src/
app.js
server.js
config.js
modules/
users/
controller.js
service.js
repository.js
shared/
errors.js
logger.jsMantenha o arquivo server.js responsável por recursos externos e o app.js por montar a aplicação. Assim, testes podem importar a aplicação sem abrir portas.
Testando CommonJS
const test = require('node:test');
const assert = require('node:assert/strict');
const { somar } = require('../src/math');
test('soma dois números', () => {
assert.equal(somar(2, 3), 5);
});Para substituir dependências, prefira injeção explícita:
function criarService({ repository, clock }) {
return {
async executar(id) {
return repository.salvar({ id, createdAt: clock.now() });
}
};
}
module.exports = { criarService };Segurança ao carregar módulos
Nunca use entrada do usuário diretamente em require():
// perigoso
const plugin = require(req.query.module);Use uma allowlist fechada:
const plugins = {
pdf: './plugins/pdf',
csv: './plugins/csv'
};
const escolhido = plugins[tipo];
if (!escolhido) throw new Error('Plugin inválido');
const plugin = require(escolhido);Limpeza de cache em testes
Excluir itens de require.cache pode forçar nova execução, mas gera testes frágeis. Prefira fábricas e dependências injetáveis.
CommonJS em bibliotecas
Pacotes podem publicar uma entrada CommonJS e outra ESM com conditional exports. É importante garantir que ambas representem a mesma API e não criem duas instâncias independentes de estado.
Veja Conditional Exports no Node.js e Package Exports no Node.js.
Migrando para ESM
- adicione testes;
- remova efeitos colaterais dos imports;
- substitua padrões globais por fábricas;
- mapeie usos de
__dirnameerequire.resolve; - converta um pacote por vez;
- adicione extensões a caminhos relativos;
- avalie dependências CommonJS;
- execute testes e benchmarks.
O guia ESM no Node.js detalha a configuração moderna.
Erros comuns
- reatribuir
exportse esperar alterarmodule.exports; - guardar estado global mutável no cache;
- abrir conexões durante o carregamento;
- depender da ordem de ciclos;
- carregar módulos a partir de entrada externa;
- importar arquivos internos de pacotes;
- limpar
require.cachecomo estratégia principal de testes; - misturar sintaxes sem uma fronteira clara.
Checklist para produção
- use
node:para módulos internos; - mantenha exports pequenos;
- evite efeitos colaterais;
- injete recursos externos;
- documente o formato do pacote;
- teste ciclos e inicialização;
- valide configuração;
- planeje interoperabilidade;
- monitore tempo de startup.
Conclusão
CommonJS no Node.js continua relevante para manutenção, interoperabilidade e publicação de bibliotecas. Seu modelo síncrono e o cache de módulos são simples, mas exigem cuidado com estado compartilhado, efeitos colaterais e ciclos.
Projetos bem estruturados podem continuar usando CommonJS com segurança ou migrar gradualmente para ESM. O ponto principal é tornar dependências explícitas, reduzir lógica executada durante o carregamento e proteger a API pública.
Consulte a documentação oficial de módulos CommonJS do Node.js e a documentação de pacotes do Node.js.



