Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

CommonJS no Node.js: Guia Prático

Atualizado em: 4 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Durante muitos anos, o CommonJS foi o principal sistema de módulos do Node.js. Ele usa require() para carregar dependências e module.exports para publicar valores. Mesmo com a adoção de ES Modules, uma grande quantidade de aplicações, scripts e pacotes ainda depende do CommonJS no Node.js.

O formato parece simples, mas possui detalhes importantes: módulos são encapsulados, carregados de forma síncrona e armazenados em cache. Alterar exports incorretamente, criar ciclos ou misturar CommonJS e ESM sem entender a interoperabilidade pode causar resultados inesperados.

Neste guia, você aprenderá a criar módulos, usar exports, entender resolução e cache, carregar JSON, lidar com ciclos, invalidar cache em testes e migrar gradualmente para ES Modules.

Primeiro módulo CommonJS

// math.js
function sum(a, b) {
  return a + b;
}

module.exports = {
  sum
};

No consumidor:

const { sum } = require('./math');

console.log(sum(2, 3));

A extensão .js pode ser omitida em muitos imports CommonJS. O Node.js aplica regras de resolução para encontrar arquivo ou diretório.

A documentação oficial de CommonJS explica resolução, wrapper e cache. Para comparar com o padrão moderno, veja a documentação de ES Modules.

Consulte também o que é Node.js e o que é JavaScript.

module.exports e exports

exports começa como uma referência para module.exports:

exports.sum = (a, b) => a + b;
exports.subtract = (a, b) => a - b;

Porém, atribuir outro valor diretamente a exports quebra a referência:

exports = function service() {};

Esse código não substitui o valor exportado. Para publicar uma função única:

module.exports = function service() {
  return 'ok';
};

O wrapper do módulo

Antes da execução, o Node.js envolve o arquivo em uma função semelhante a:

(function(exports, require, module, __filename, __dirname) {
  // conteúdo do arquivo
});

Por isso, variáveis declaradas no nível superior não ficam globais e os identificadores __filename e __dirname estão disponíveis.

Caminhos relativos

const config = require('./config');
const helper = require('../shared/helper');

Caminhos iniciados por ponto são resolvidos em relação ao arquivo atual, não ao diretório de execução. Para arquivos de dados, use path.join(__dirname, ...):

const path = require('node:path');

const file = path.join(__dirname, 'templates', 'email.html');

O artigo de File System no Node.js mostra como validar caminhos.

Módulos nativos

const fs = require('node:fs');
const path = require('node:path');

O prefixo node: deixa explícito que a dependência pertence à plataforma.

Pacotes de node_modules

const express = require('express');

O Node.js procura o pacote em diretórios node_modules ascendentes. O campo exports do pacote pode restringir subcaminhos disponíveis.

Carregando JSON

const config = require('./config.json');

O JSON é lido, analisado e armazenado em cache. Alterar o arquivo no disco não atualiza automaticamente o objeto. Para configuração que muda em tempo de execução, leia com fs e valide o schema.

Cache de módulos

const first = require('./service');
const second = require('./service');

console.log(first === second); // true

Após a primeira execução, o valor exportado é reutilizado. Isso permite singletons, mas também mantém estado mutável entre consumidores e testes.

Estado compartilhado

// counter.js
let value = 0;

module.exports = {
  increment() {
    value++;
  },
  get() {
    return value;
  }
};

Todos os consumidores da mesma instância do módulo enxergam o contador. Prefira factories quando cada contexto precisa de estado isolado:

module.exports = function createCounter() {
  let value = 0;

  return {
    increment: () => ++value,
    get: () => value
  };
};

Inspecionando e removendo cache

const id = require.resolve('./config');
delete require.cache[id];

Remover uma entrada não descarrega automaticamente toda a árvore de dependências. Use esse recurso com cautela, geralmente em ferramentas e testes. Em aplicações, prefira APIs explícitas de reload.

require.resolve()

const location = require.resolve('some-package');
console.log(location);

A função informa o arquivo que seria carregado sem executá-lo. Isso é útil em diagnóstico e plugins, mas não deve ser usado para acessar internals não públicos.

Carregamento síncrono

require() carrega e avalia o módulo de forma síncrona. Dependências no início da aplicação são adequadas, mas carregar código pesado durante uma requisição pode aumentar latência.

Para carregamento assíncrono e condicional, CommonJS pode usar import():

async function loadModule() {
  const module = await import('./modern-module.js');
  return module.default;
}

Ciclos de dependência

Quando A requer B e B requer A, um deles pode receber exports parcialmente inicializados:

// a.js
exports.ready = false;
const b = require('./b');
exports.ready = true;

Ciclos tornam a ordem de inicialização difícil de prever. Extraia contratos compartilhados, use injeção de dependência ou reorganize responsabilidades.

Interoperabilidade com ES Modules

Um ES Module pode importar CommonJS como default:

import legacy from './legacy.cjs';

Um módulo CommonJS não pode usar require() diretamente para carregar todo ES Module. Use import() assíncrono:

const modern = await import('./modern.js');

Veja o guia de ES Modules no Node.js.

main e exports no package.json

{
  "main": "./index.js",
  "exports": {
    ".": "./index.js",
    "./client": "./client.js"
  }
}

main define a entrada tradicional. exports controla a API pública e pode bloquear imports profundos.

Pacote dual

Uma biblioteca pode oferecer arquivos para require e import. Contudo, duas versões podem criar instâncias separadas do estado interno. Teste singletons, classes e símbolos compartilhados.

Plugins

Não aceite um nome arbitrário e passe para require(). Use um mapa permitido:

const plugins = {
  csv: './plugins/csv',
  json: './plugins/json'
};

function loadPlugin(name) {
  const target = plugins[name];
  if (!target) throw new Error('Plugin inválido');
  return require(target);
}

Isso evita carregar arquivos ou pacotes inesperados.

Mocks e testes

Como dependências são capturadas ao carregar o módulo, substituir um objeto depois pode não afetar referências internas. Prefira injeção:

module.exports = function createService({ repository, logger }) {
  return {
    async run() {
      const data = await repository.load();
      logger.info('loaded');
      return data;
    }
  };
};

O Node Test Runner oferece mocks e hooks.

Migrando para ESM

Uma migração segura inclui:

  • definir a versão mínima do Node.js;
  • identificar pacotes ESM-only;
  • trocar exports e imports por etapas;
  • renomear pontes para .cjs;
  • adicionar extensões aos imports ESM;
  • substituir __dirname quando necessário;
  • validar scripts, testes e ferramentas;
  • revisar o contrato público da biblioteca.

Quando manter CommonJS

Projetos estáveis, scripts legados e ambientes com ferramentas antigas podem continuar usando CommonJS. Não migre apenas por moda. Avalie compatibilidade, manutenção e benefícios reais.

Erros comuns

  • Atribuir valor diretamente a exports.
  • Depender de estado global no cache.
  • Remover cache sem entender dependências.
  • Criar ciclos entre módulos.
  • Carregar entrada do usuário com require().
  • Assumir que JSON será recarregado.
  • Usar imports profundos de pacote.
  • Misturar ESM e CommonJS sem testes.

Boas práticas

  • Use module.exports para substituir o valor.
  • Prefira factories para estado isolado.
  • Use node: em módulos nativos.
  • Evite ciclos.
  • Defina a API pública com exports.
  • Não carregue caminhos arbitrários.
  • Injete dependências para testar.
  • Documente efeitos do cache.
  • Use import() para ESM.
  • Migre somente com plano e testes.

Conclusão

O CommonJS no Node.js continua relevante em aplicações e pacotes. Seu modelo síncrono, cache e sintaxe simples tornam o formato previsível quando suas regras são compreendidas.

O cuidado principal está em exports, estado compartilhado e interoperabilidade. Com módulos pequenos, factories e uma API pública clara, CommonJS pode coexistir com ES Modules durante uma migração gradual ou permanecer como base de projetos estáveis.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita