Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
Erro resolvidoIniciantecódigo testado

Error: Cannot find module no Node.js: causas e correção

As seis causas do Cannot find module no Node, do npm install esquecido ao caminho sem extensão em ESM, cada uma reproduzida com o stack trace real.

Rodolfo Mori9 min de leitura

Cannot find module significa que o resolvedor de módulos do Node tentou transformar um nome de importação em um arquivo e não encontrou um destino válido. A correção depende de o que estava sendo resolvido: um pacote do npm ou um caminho do seu próprio projeto. O campo code, o nome procurado e a pilha de importações entregam essa diferença.

Pense numa encomenda com endereço incompleto. Se o destinatário é express, o Node procura o pacote nas pastas node_modules permitidas. Se o endereço começa com ./, ele parte da pasta do arquivo que importou e segue o caminho relativo. Nome errado, extensão ausente ou pacote não instalado levam a buscas diferentes, mas terminam no mesmo aviso: não houve arquivo para carregar. É por isso que o primeiro passo é ler o endereço do erro, não reinstalar tudo no escuro.

Este artigo reproduz seis causas frequentes num projeto real rodando no Node 24.16.0 e mostra como identificar qual busca falhou.

O projeto é a API da biblioteca do bairro: acervo, empréstimos e um servidor Express. Ele vive em /private/tmp/biblioteca, e é dele que sai cada stack trace daqui para baixo.

Leia o código do erro antes de tocar no projeto

O Node tem dois carregadores de módulo, e eles falham com mensagens diferentes. Este é o mesmo import express faltando, escrito nos dois formatos. Primeiro em CommonJS, no arquivo src/servidor.cjs:

js
const express = require('express');

const app = express();
app.get('/livros', (req, res) => res.json([{ titulo: 'Dom Casmurro' }]));
app.listen(4477);
node:internal/modules/cjs/loader:1503 throw err; ^

Error: Cannot find module ‘express’ Require stack:

  • /private/tmp/biblioteca/src/servidor.cjs at Module._resolveFilename (node:internal/modules/cjs/loader:1500:15) at wrapResolveFilename (node:internal/modules/cjs/loader:1071:27) at defaultResolveImplForCJSLoading (node:internal/modules/cjs/loader:1095:10) at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1116:12) at Module._load (node:internal/modules/cjs/loader:1285:25) at wrapModuleLoad (node:internal/modules/cjs/loader:255:19) at Module.require (node:internal/modules/cjs/loader:1600:12) at require (node:internal/modules/helpers:153:16) at Object.<anonymous> (/private/tmp/biblioteca/src/servidor.cjs:1:17) at Module._compile (node:internal/modules/cjs/loader:1854:14) { code: ‘MODULE_NOT_FOUND’, requireStack: [ ‘/private/tmp/biblioteca/src/servidor.cjs’ ] }

Node.js v24.16.0

Agora o mesmo pacote faltando, no arquivo ESM src/servidor.js:

js
import express from 'express';

const app = express();
app.get('/livros', (req, res) => res.json([{ titulo: 'Dom Casmurro' }]));
app.listen(4477);
node:internal/modules/package_json_reader:301 throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null); ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package ‘express’ imported from /private/tmp/biblioteca/src/servidor.js at Object.getPackageJSONURL (node:internal/modules/package_json_reader:301:9) at packageResolve (node:internal/modules/esm/resolve:768:81) at moduleResolve (node:internal/modules/esm/resolve:859:18)

Repare na diferença que vale ouro: o ESM escreveu package, não module. Ele só usa a palavra “package” quando o que faltou é um pacote do npm. Quando o que faltou é um arquivo seu, a mensagem muda para “module” e vem com o caminho absoluto exato que ele tentou abrir.

a mensagem diz o Node estava procurando por onde começar
MODULE_NOT_FOUND + Require stack pacote, nas pastas node_modules subindo a árvore ler a requireStack de baixo para cima: o último arquivo é quem pediu
ERR_MODULE_NOT_FOUND · Cannot find package pacote, do mesmo jeito package.json e npm ci
ERR_MODULE_NOT_FOUND · Cannot find module /caminho aquele arquivo, e só ele comparar o caminho impresso com o que existe no disco

A diferença entre os dois carregadores está explicada em ESM ou CommonJS no Node. Para o resto deste artigo, o projeto da biblioteca está com "type": "module" no package.json.

Causa 1: o pacote nunca foi instalado nesta máquina

É o caso mais comum e o mais rápido: você clonou o repositório e rodou o servidor antes de instalar as dependências. O package.json lista o Express, a pasta node_modules não existe.

bash
npm ci
node src/servidor.js
Biblioteca no ar em http://localhost:4477

Um detalhe que engana muita gente: node_modules existir não quer dizer que o pacote certo está lá dentro. O Node procura subindo a árvore de pastas, então uma node_modules de um projeto vizinho, ou da sua pasta pessoal, pode fazer o import funcionar hoje e quebrar amanhã em outra máquina. Quem manda é esta lista:

js
console.log(module.paths.join('\n'));
/private/tmp/biblioteca/node_modules /private/tmp/node_modules /private/node_modules /node_modules

Causa 2: em ESM o caminho relativo precisa da extensão .js

Aqui o Node acha o pacote, mas não acha o seu arquivo. O servidor.js importa a rota de livros sem escrever .js no fim:

js
import express from 'express';
import { listarLivros } from './rotas/livros';

const app = express();
app.get('/livros', listarLivros);
app.listen(4477, () => console.log('Biblioteca no ar em http://localhost:4477'));
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/private/tmp/biblioteca/src/rotas/livros' imported from /private/tmp/biblioteca/src/servidor.js at finalizeResolution (node:internal/modules/esm/resolve:271:11) at moduleResolve (node:internal/modules/esm/resolve:865:10) at defaultResolve (node:internal/modules/esm/resolve:992:11) { code: 'ERR_MODULE_NOT_FOUND', url: 'file:///private/tmp/biblioteca/src/rotas/livros' }

O campo url entrega o diagnóstico inteiro: o Node procurou um arquivo chamado livros, sem extensão nenhuma, e é isso que ele quis dizer literalmente. Em módulos com import e export, o especificador relativo é uma URL — e URL não adivinha extensão. CommonJS adivinha, e é por isso que o mesmo código funciona em .cjs:

js
const { acervo } = require('./rotas/livros');

console.log(`CommonJS achou sem extensão: ${acervo.length} livros`);
CommonJS achou sem extensão: 2 livros

A correção é escrever './rotas/livros.js'. Nada de flag: a --experimental-specifier-resolution=node, que aparece em resposta antiga de fórum, é aceita pelo Node 24 sem erro e simplesmente não faz mais efeito nenhum — testei, e o import continua quebrando igual.

Causa 3: maiúscula no nome do arquivo passa no Mac e quebra no Linux

Este é o erro que ninguém reproduz no próprio computador. O arquivo no disco se chama livros.js, e o import escreveu Livros.js:

js
import { acervo } from './rotas/Livros.js';

console.log(`Acervo carregado: ${acervo.length} livros`);

No macOS com APFS padrão, isso roda:

Acervo carregado: 2 livros

Para provar o que acontece no servidor, criei um volume APFS case-sensitive com hdiutil, copiei o projeto inteiro para lá e rodei o mesmo arquivo:

bash
diskutil info /Volumes/CaseSens | grep "File System Personality"
node src/caso.js
File System Personality: Case-sensitive APFS Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Volumes/CaseSens/biblioteca/src/rotas/Livros.js' imported from /Volumes/CaseSens/biblioteca/src/caso.js at finalizeResolution (node:internal/modules/esm/resolve:271:11) at moduleResolve (node:internal/modules/esm/resolve:865:10) { code: 'ERR_MODULE_NOT_FOUND', url: 'file:///Volumes/CaseSens/biblioteca/src/rotas/Livros.js' }

Mesmo projeto, mesmo Node, mesma linha de código. Só mudou o sistema de arquivos. É exatamente isso que acontece quando o deploy roda numa imagem Linux e o seu Mac dizia que estava tudo bem.

Como o Git costuma ignorar a troca de caixa, vale ter um verificador no package.json que compara cada import relativo com o nome real no disco:

js
import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';

const raiz = path.join(import.meta.dirname, '..', 'src');
const importRelativo = /from\s+['"](\.[^'"]+)['"]/g;

async function listarArquivos(pasta) {
  const entradas = await readdir(pasta, { withFileTypes: true });
  const saida = [];
  for (const entrada of entradas) {
    const caminho = path.join(pasta, entrada.name);
    if (entrada.isDirectory()) saida.push(...(await listarArquivos(caminho)));
    else if (caminho.endsWith('.js')) saida.push(caminho);
  }
  return saida;
}

let problemas = 0;
for (const arquivo of await listarArquivos(raiz)) {
  const codigo = await readFile(arquivo, 'utf8');
  for (const [, especificador] of codigo.matchAll(importRelativo)) {
    const alvo = path.resolve(path.dirname(arquivo), especificador);
    const vizinhos = await readdir(path.dirname(alvo));
    if (!vizinhos.includes(path.basename(alvo))) {
      problemas++;
      console.log(`caixa errada: ${path.relative(raiz, arquivo)} importa "${especificador}"`);
    }
  }
}

process.exit(problemas === 0 ? 0 : 1);
caixa errada: caso.js importa "./rotas/Livros.js"

Ele funciona porque readdir devolve o nome real gravado no disco, mesmo num sistema que não diferencia caixa na hora de abrir. Depois de corrigir o import para livros.js, o script sai calado e com código 0.

Causa 4: node_modules velho depois de trocar de branch

Você trocou de branch, o colega adicionou o Zod para validar o empréstimo, e o seu node_modules continua sendo o de ontem:

js
import { z } from 'zod';

export const esquemaEmprestimo = z.object({
  livroId: z.number().int().positive(),
  leitor: z.string().min(3),
});
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'zod' imported from /private/tmp/biblioteca/src/rotas/emprestimos.js at Object.getPackageJSONURL (node:internal/modules/package_json_reader:301:9) at packageResolve (node:internal/modules/esm/resolve:768:81) at moduleResolve (node:internal/modules/esm/resolve:859:18) { code: 'ERR_MODULE_NOT_FOUND' }

Um comando confirma o desencontro entre o que o package.json promete e o que está instalado, e ele sai com código 1 — dá para pôr no CI:

bash
npm ls --depth=0
biblioteca@1.0.0 /private/tmp/biblioteca ├── express@5.2.1 └── UNMET DEPENDENCY zod@^4.1.12

npm error code ELSPROBLEMS npm error missing: zod@^4.1.12, required by biblioteca@1.0.0

O contrário também acontece, e é pior porque não dá erro nenhum na sua máquina. Alguém instalou um pacote sem salvar no package.json, o código passou a usar, e tudo funciona local:

bash
npm install --no-save nanoid
node -e "import('nanoid').then(m => console.log('codigo do emprestimo:', m.nanoid(8)))"
npm ls --depth=0
codigo do emprestimo: s9tBXV3F biblioteca@1.0.0 /private/tmp/biblioteca ├── dotenv@17.4.2 ├── express@5.2.1 ├── nanoid@6.0.1 extraneous └── zod@4.4.3

extraneous é o aviso de que aquele pacote está no disco e não está no contrato. O npm ci apaga a node_modules e reinstala só o que o package-lock.json manda — e o mesmo código morre:

bash
npm ci
node -e "import('nanoid')"
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'nanoid' imported from /private/tmp/biblioteca/[eval] at Object.getPackageJSONURL (node:internal/modules/package_json_reader:301:9) at packageResolve (node:internal/modules/esm/resolve:768:81)

Esse é o “funciona na minha máquina” em estado puro. Se você quer descobrir o problema antes do CI, use npm ci também no seu computador quando trocar de branch — ele é o comando que respeita o package.json e o lock ao pé da letra.

Causa 5: dependência de dev num deploy que rodou –omit=dev

O src/config.js da biblioteca lia o .env com o pacote dotenv:

js
import 'dotenv/config';

export const config = {
  porta: Number(process.env.PORTA ?? 4477),
  acervoMaximo: Number(process.env.ACERVO_MAXIMO ?? 5000),
};

Local funciona, porque npm install instala tudo. O deploy roda o comando que economiza imagem, e aí o dotenv não vem junto:

bash
npm ci --omit=dev
node -e "import('./src/config.js')"
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'dotenv' imported from /private/tmp/biblioteca/src/config.js at Object.getPackageJSONURL (node:internal/modules/package_json_reader:301:9) at packageResolve (node:internal/modules/esm/resolve:768:81) at moduleResolve (node:internal/modules/esm/resolve:859:18)

A regra é simples: se o código que roda em produção importa o pacote, ele é dependencies. devDependencies é para o que só existe na sua máquina — linter, ferramenta de teste, nodemon.

Neste caso específico há uma saída melhor ainda, que é apagar a dependência: o Node lê arquivo .env sozinho desde a versão 20.6, com --env-file. O config.js fica sem nenhum import:

js
export const config = {
  porta: Number(process.env.PORTA ?? 4477),
  acervoMaximo: Number(process.env.ACERVO_MAXIMO ?? 5000),
};
bash
node --env-file=.env -e "import('./src/config.js').then(m => console.log(m.config))"
node -e "import('./src/config.js').then(m => console.log(m.config))"
{ porta: 4477, acervoMaximo: 8000 } { porta: 4477, acervoMaximo: 5000 }

A segunda linha é o mesmo arquivo sem a flag: sem .env carregado, os valores padrão entram. Tem mais sobre isso em variáveis de ambiente no Node.

Causa 6: caminho montado com process.cwd() em vez de import.meta.dirname

A biblioteca registra as rotas com import() dinâmico, montando o caminho a partir da pasta onde o comando foi digitado:

js
import path from 'node:path';

const arquivosDeRota = ['livros.js', 'emprestimos.js'];

export async function carregarRotas() {
  const carregadas = [];
  for (const arquivo of arquivosDeRota) {
    const caminho = path.join(process.cwd(), 'src', 'rotas', arquivo);
    await import(caminho);
    carregadas.push(arquivo);
  }
  return carregadas;
}

Da raiz do projeto, funciona. De dentro de src/, não:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/private/tmp/biblioteca/src/src/rotas/livros.js' imported from /private/tmp/biblioteca/src/carregar-rotas.js at finalizeResolution (node:internal/modules/esm/resolve:271:11) at moduleResolve (node:internal/modules/esm/resolve:865:10) at defaultResolve (node:internal/modules/esm/resolve:992:11)

O src/src no meio do caminho é a resposta inteira. process.cwd() é a pasta do terminal, não a pasta do arquivo — e ela muda quando o systemd, o Docker ou um script npm sobe o processo de outro lugar. Trocando por import.meta.dirname, que é a pasta do próprio módulo, o caminho para de depender de onde você está:

js
const caminho = path.join(import.meta.dirname, 'rotas', arquivo);
rotas carregadas: [ 'livros.js', 'emprestimos.js' ] rotas carregadas: [ 'livros.js', 'emprestimos.js' ] rotas carregadas: [ 'livros.js', 'emprestimos.js' ]

São três execuções do mesmo arquivo, da raiz do projeto, de dentro de src/ e de /Users. Se você tentar usar __dirname num arquivo ESM, o erro é outro e a mensagem já entrega a correção:

ReferenceError: __dirname is not defined in ES module scope This file is being treated as an ES module because it has a '.js' file extension and '/private/tmp/biblioteca/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.

Roteiro de diagnóstico em três comandos

Quando nenhuma das seis causas saltar aos olhos, pare de chutar e pergunte ao Node onde ele procurou. A variável NODE_DEBUG=module imprime a lista completa de pastas antes de falhar:

bash
NODE_DEBUG=module node -e "require('dotenv')" 2>&1 | grep dotenv
MODULE 75273: Module._load REQUEST dotenv parent: [eval] MODULE 75273: looking for "dotenv" in ["/private/tmp/biblioteca/node_modules","/private/tmp/node_modules","/private/node_modules","/node_modules","/Users/rodolfomori/.node_modules","/Users/rodolfomori/.node_libraries","/Users/rodolfomori/.nvm/versions/node/v24.16.0/lib/node"] Error: Cannot find module 'dotenv'

Se o pacote está instalado, o segundo comando mostra qual arquivo o Node vai abrir de fato — útil quando existe mais de uma cópia da mesma biblioteca no monorepo:

bash
node -e "console.log(require.resolve('express'))"
node --input-type=module -e "console.log(import.meta.resolve('./src/rotas/livros.js'))"
/private/tmp/biblioteca/node_modules/express/index.js file:///private/tmp/biblioteca/src/rotas/livros.js

E o terceiro é o npm ls --depth=0 da causa 4, que compara o prometido com o instalado. Na ordem: NODE_DEBUG diz onde ele procurou, resolve diz o que ele achou, npm ls diz o que deveria estar lá.

O próximo passo

Com o servidor subindo de novo, vale fechar o buraco que deixou o erro entrar: rode npm ci sempre que trocar de branch, e ponha npm ls --depth=0 no seu pipeline. Se o erro apareceu montando a API, a primeira rota no Express mostra a estrutura de pastas que evita import relativo comprido, e o deploy de API Node trata do --omit=dev e do Dockerfile. O caminho completo, do zero à API no ar, está no guia de Node.js.

Antes de encerrar, rode o roteiro no seu projeto: npm ls --depth=0, require.resolve ou import.meta.resolve para o módulo suspeito e, se ainda falhar, NODE_DEBUG=module. Anote o nome exato procurado e a primeira pasta em que ele deveria existir. A prática está concluída quando você consegue apontar uma causa concreta — dependência ausente, caminho incorreto ou diferença entre ambientes — sem apagar node_modules por tentativa.

  • node
  • erro
  • cannot find module
  • modulos
  • npm

Perguntas frequentes

Instalei o pacote com npm install -g e o erro continua. Por quê?
Porque a pasta global do npm não está na lista de busca. Compare npm root -g com o que node -e "console.log(module.paths)" imprime — a global termina em lib/node_modules e a lista do Node tem só lib/node. Pacote que o seu código importa vai como dependência do projeto; global é para CLI que você digita no terminal.
Existe alguma flag para o ESM voltar a achar arquivo sem extensão?
Não mais. A --experimental-specifier-resolution=node que aparece em resposta antiga de fórum ainda é aceita pelo Node 24 sem reclamar, e não faz efeito nenhum — o mesmo import continua falhando. Escreva a extensão ou declare um imports no package.json.
Importei um .json e o Node reclamou. É o mesmo erro?
Não. Importar JSON em ESM sem atributo dá TypeError [ERR_IMPORT_ATTRIBUTE_MISSING], dizendo que o módulo precisa de type: json. O arquivo foi encontrado; o que faltou foi import acervo from './dados/acervo.json' with { type: 'json' }.
O erro só acontece dentro do Docker. O que investigar primeiro?
A imagem quase sempre é Linux, então comece pela caixa alta e baixa dos nomes de arquivo, que o seu Mac ignora e o container não. Depois confira se o build roda npm ci --omit=dev enquanto algum import de produção aponta para pacote que está em devDependencies.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. Node.js — Modules: ECMAScript modules — nodejs.org
  2. Node.js — Modules: CommonJS modules — nodejs.org
  3. npm Docs — npm ci — docs.npmjs.com

Continue por aqui