module.exports é o valor que um arquivo Node.js entrega para qualquer outro arquivo que o carregue com require(). Ele começa como um objeto vazio: adicione propriedades para exportar várias coisas, ou atribua um valor novo (uma função, uma classe) para exportar uma coisa só. exports é uma variável de atalho que aponta para o mesmo objeto, então exports.add = ... funciona, mas exports = ... quebra a ligação e não exporta nada.
Ele imprimiu 5 20 3.14159, depois [ 'add', 'multiply', 'PI' ]. As três linhas de math.js adicionaram propriedades ao mesmo objeto, seja passando por module.exports ou por exports, e require('./math') retornou esse objeto.
Exportar uma função, uma classe ou um objeto
Quando um módulo tem uma coisa principal, atribua direto a module.exports. Quem chama recebe a própria função, não um objeto em volta dela:
Ele imprimiu Hello, Ada e Grace (user). Se você vir TypeError: greet is not a function, quem chamou usou o formato errado: escreva const { greet } = require(...) para uma propriedade de objeto e const greet = require(...) para um valor atribuído.
module.exports vs exports: a armadilha da reatribuição
Antes de o seu arquivo rodar, o Node.js faz exports = module.exports. Os dois nomes apontam para um único objeto, e require retorna module.exports. Atribuir um valor novo a exports só muda a variável local:
broken.js exportou {}, porque exports agora aponta para um objeto novo que ninguém retorna. working.js exportou { version: 2 }.
| Você escreve | Exportado? |
|---|---|
module.exports.x = 1 | Sim |
exports.x = 1 | Sim, mesmo objeto |
module.exports = { x: 1 } | Sim, substitui o objeto |
exports = { x: 1 } | Não, o valor se perde |
A regra mais simples: escreva sempre module.exports. Funciona em todos os casos, e o código diz o que faz.
require retorna o mesmo objeto em cache
O Node.js roda o código de um módulo uma vez, na primeira vez que ele é chamado com require, e guarda module.exports em cache. Todo require seguinte do mesmo arquivo retorna esse mesmo objeto, então o estado dentro de um módulo é compartilhado por todos os arquivos que o carregam:
counter.js is running apareceu uma vez, b.next() retornou 3, e a === b é true, porque a chave do cache é o caminho resolvido do arquivo, e ./counter e ./counter.js resolvem para o mesmo arquivo. É por isso que uma conexão com banco de dados ou um objeto de configuração criado em um módulo é compartilhado pelo app inteiro.
module.exports e ES modules
Os ES modules usam export e import. As formas se correspondem assim:
// CommonJS // ES modules
module.exports = greet; export default greet;
module.exports.add = add; export { add };
const greet = require('./greet'); import greet from './greet.js';
const { add } = require('./math'); import { add } from './math.js';
Um ES module pode fazer import de um arquivo CommonJS: o module.exports dele chega como o import default. Desde o Node.js 22.12, require() também consegue carregar um ES module que não tenha await no topo. Para saber quando escolher cada um, veja CommonJS vs ES modules.
Erros comuns
Misturar exports.x com module.exports = ... no mesmo arquivo. A atribuição substitui o objeto, então todo exports.x escrito antes dela se perde. Escolha um estilo por arquivo.
Exportar um valor que muda depois. module.exports = { count } copia o número no momento do carregamento; quando o módulo atualiza count, quem chamou continua vendo o valor antigo. Exporte uma função que o retorne, como counter.js faz acima.
Esquecer o ./. require('math') procura um pacote em node_modules, não o math.js ao lado do seu arquivo. Arquivos locais sempre começam com ./ ou ../.
Perguntas frequentes
Posso usar module.exports e export no mesmo arquivo?
Não. Um arquivo é CommonJS (require, module.exports) ou ES module (import, export). O Node.js decide pela extensão (.cjs ou .mjs) ou pelo "type" no package.json.
Por que o require retorna um objeto vazio?
Normalmente o módulo fez exports = ... em vez de module.exports = ..., ou dois módulos se importam um ao outro. Em um require circular, um deles recebe o module.exports do outro antes de esse arquivo terminar de rodar.
Preciso da extensão .js no require?
Não. require('./math') tenta ./math, depois ./math.js, ./math.json e ./math.node, depois uma pasta ./math com um index.js. Em um import de ES module a extensão é obrigatória.
Como recarregar um módulo que o require já colocou em cache?
Apague a entrada com delete require.cache[require.resolve('./math')] e chame o require de novo. Isso serve mais para testes e ferramentas de desenvolvimento; código de aplicação não deve depender disso.