Melhores Práticas de Scripts Node.js
Um resumo condensado das 25 melhores práticas mais importantes extraídas de todas as páginas desta seção.
Busque em todas as páginas da documentação
Um resumo condensado das 25 melhores práticas mais importantes extraídas de todas as páginas desta seção.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
typescript-eslint (o meta-pacote) em vez de @typescript-eslint/parser e @typescript-eslint/eslint-plugin separados, para que você obtenha tseslint.config() e o parser empacotado configurado de forma consistente.no-floating-promises e no-misused-promises não fazem nada silenciosamente sem parserOptions.project; defina-o (e tsconfigRootDir: import.meta.dirname) para que os caminhos sejam resolvidos em relação à configuração, não ao cwd.eslint-config-prettier deve ser a última entrada ou seus desativações de regras serão substituídas, e um bloco { ignores: […] } atua como um ignore global apenas quando é a única chave em seu objeto - misturá-lo com rules o transforma em um filtro por arquivo..mjs é sempre ESM, .cjs é sempre CommonJS, e .js sem extensão segue o campo "type" do package.json mais próximo (com padrão CommonJS); inverter "type": "module" converte todos os .js abaixo e exige renomear os holdouts CJS para .cjs.ERR_MODULE_NOT_FOUND sem a extensão - import { helper } from "./utils.js" mesmo quando a origem é utils.ts - porque sob "module": "NodeNext" o especificador modela o caminho de tempo de execução emitido.__dirname, __filename e require não existem no escopo ESM - const __dirname = path.dirname(fileURLToPath(import.meta.url)) - em vez de copiar e colar código CJS que lança erros silenciosamente."module": "NodeNext" quanto "moduleResolution": "NodeNext" para que o TypeScript modele fielmente a resolução real do Node - incluindo condições exports, .mts/.cts e a extensão .js necessária.EventEmitter trata 'error' de forma especial - emitir sem um listener registrado trava o processo com uma exceção não capturada, então anexe um manipulador (mesmo que apenas para log) em cada emitter que você possuir.emitter.off(event, fn) remove apenas a referência exata da função que você registrou - const handler = () => { ... }; emitter.on("data", handler); emitter.off("data", handler) - funções de seta anônimas nunca correspondem, então salve o manipulador em uma variável nomeada.MaxListeners é 10 e o aviso dispara uma vez em 11, o que é um sinal de vazamento fácil de perder; corrija o excesso de registro, aumente o limite intencionalmente com setMaxListeners(), ou use EventEmitter.defaultMaxListeners.node:http sem um limite de bytes é um vetor de ataque OOM; acumule em um contador de comprimento e responda 413 quando ultrapassar um limite (Express usa express.json({ limit }), Fastify tem bodyLimit).app.get("/", (req, res, next) => { handleAsync(req, res).catch(next) }) - ou atualize para o Express 5, que encaminha para o middleware de erro nativamente.res.send/res.json; o Fastify envia o que quer que você retorne do manipulador - misturar os dois estilos (retornar dados no Express, chamar reply.send no Fastify) produz respostas pendentes ou duplicadas.sudo completamente e permitem que você alterne versões do Node por projeto; executar o instalador oficial mais sudo npm install -g leva diretamente à miséria de EACCES."packageManager": "pnpm@x.y.z" em package.json para que o Corepack (incluído com Node 18.17+) imponha a ferramenta e a versão exatas em todos os contribuidores e runners de CI, eliminando a deriva de instalação "funciona na minha máquina".node_modules plano e içado do npm esconde dependências fantasmas (importações não listadas em package.json); a migração para o layout estrito de symlinks do pnpm as expõe como erros reais - corrija-as adicionando as dependências, não mudando para node-linker=hoisted.parseArgs embutido lida com opções sem adicionar uma dependência - const { values } = parseArgs({ args: process.argv.slice(2), options: { port: { type: "string", default: "3000" } } }) - e ele ignora automaticamente as entradas do caminho do node e do script.npm run cli --flag passa --flag para o próprio npm, não para seu script; use npm run cli -- --flag para que a flag alcance o comando subjacente (pnpm e yarn se comportam da mesma maneira).tsx inicia em ~100ms usando esbuild e é ideal para scripts locais; a produção deve executar JavaScript compilado via tsc + node para que não haja dependência do runner e o custo de inicialização permaneça estável.tsx, ts-node e node --experimental-strip-types todos removem tipos e entregam JavaScript para o V8 - nenhum deles detecta erros de tipo em tempo de execução, então execute tsc --noEmit em CI para segurança real.@types/node como devDependency, process, Buffer e cada importação node:* são any, matando o autocompletar e escondendo bugs reais por trás de coerções implícitas de any silenciosas.fs.readFile(path) sem uma codificação retorna um Buffer, não uma string - const text = await readFile("config.json", "utf8") - para que .split/regex/JSON parsing downstream funcione sem um buffer surpresa.process.exit() termina imediatamente e trunca stdout pendente ou escritas assíncronas - process.exitCode = 1; return - permitir que o event loop drene preserva logs enquanto ainda falha o script.process.stdout.isTTY é falso, que é o padrão na maioria dos ambientes de CI; defina FORCE_COLOR=1 (ou equivalente) no ambiente de CI se você quiser saída de log colorida e lembre-se que Chalk v5 é apenas ESM.Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥