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á:


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().

JAVASCRIPT
// 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.

JAVASCRIPT
// 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.

JAVASCRIPT
// 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

JAVASCRIPT
// 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,
};
▶ Experimente
JAVASCRIPT
// 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; exports só 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.

JAVASCRIPT
// utils.mjs
export function square(n) {
  return n * n;
}

export const VERSION = '2.0.0';

export default function greet(name) {
  return `Hello, ${name}!`;
}
JAVASCRIPT
// 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
JSON
// package.json
{
  "type": "module"
}

▶ Exemplo:(3) Exportações nomeadas e exportações padrão

JAVASCRIPT
// 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}`;
  }
}
▶ Experimente

▶ Exemplo: Exportação e reexportação unificadas

JAVASCRIPT
// 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';
▶ Experimente

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

JAVASCRIPT
// counter.cjs — CommonJS
let count = 0;
function increment() {
  count++;
}
module.exports = { count, increment };
▶ Experimente
JAVASCRIPT
// counter.mjs — ESM
export let count = 0;
export function increment() {
  count++;
}
JAVASCRIPT
// 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)
JAVASCRIPT
// 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

JAVASCRIPT
// legacy.cjs
module.exports = { legacyMethod() { return 'old school'; } };
▶ Experimente
JAVASCRIPT
// app.mjs
import cjs from './legacy.cjs';
console.log(cjs.legacyMethod()); // old school

Quando import é um módulo CJS no ESM, o valor de module.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:

100%
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

JAVASCRIPT
// show-paths.js
console.log(module.paths);
▶ Experimente
TEXT 📖 Somente leitura
[
  '/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.

JAVASCRIPT
// counter.js
console.log('counter.js executed!');
let count = 0;
module.exports = {
  increment() { return ++count; },
  getCount() { return count; },
};
JAVASCRIPT
// 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.

JAVASCRIPT
// 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

JAVASCRIPT
// 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
▶ Experimente

Se você excluir a entrada em require.cache e, em seguida, executar require novamente, 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

JAVASCRIPT
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
▶ Experimente

▶ Exemplo: Introdução rápida com path e os

JAVASCRIPT
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`);
▶ Experimente

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

JAVASCRIPT
// a.js
exports.loaded = false;
const b = require('./b');
exports.loaded = true;
console.log('a.js - b.loaded =', b.loaded);
▶ Experimente
JAVASCRIPT
// 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);
BASH
node a.js
TEXT 📖 Somente leitura
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

JAVASCRIPT
// 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());
};
▶ Experimente
JAVASCRIPT
// format.js
exports.upper = function (str) {
  return str.toUpperCase();
};

exports.getUserDisplay = function () {
  const user = require('./user'); // Deferred require
  return `User: ${user.getName()}`;
};

O adiamento require garante 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 de exports incompletos.



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:

JAVASCRIPT
// 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 };
JAVASCRIPT
// 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 };
JAVASCRIPT
// 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');
}
BASH
node app.js
TEXT 📖 Somente leitura
[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.exports como exportação padrão), mas no CJS não é possível usar require para carregar módulos ESM; em vez disso, é necessário usar a função dinâmica import(). Recomenda-se que os projetos sigam uma única especificação de módulo.

P: require é síncrono ou assíncrono? R: Síncrono. require bloqueia a execução do código até que o módulo termine de carregar. É por isso que o Node.js recomenda colocar require no início de um arquivo e evitar chamadas frequentes de require para novos módulos no caminho de acesso frequente durante a execução.

P: Qual é a diferença entre module.exports e exports? R: exports é uma referência abreviada para module.exports. É possível adicionar propriedades usando exports.xxx = ..., mas exports = xxx quebra 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 usar module.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ê usar require.

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 exports que 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 chamadas require e desacoplar eventos.

P: Por que as instruções import devem 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ção import().

P: O que acontece quando você usa require para carregar um arquivo JSON? R: O Node.js lê o arquivo JSON e o analisa automaticamente usando JSON.parse(), retornando um objeto JavaScript analisado. Isso é comumente usado para carregar arquivos de configuração.


📖 Resumo


📝 Exercícios

  1. Crie o módulo calculator.js, exporte as quatro funções add, subtract, multiply e divide e, em seguida, importe-as e utilize-as no main.js
  2. Converta a pergunta anterior para uma versão ESM: use a sintaxe export e o sufixo .mjs, e carregue-a usando import.
  3. 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 de require.cache.
  4. 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.
  5. Use os módulos path e os para exibir a plataforma atual do sistema operacional, o número de núcleos da CPU e o caminho absoluto do diretório atual.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%