Mejores prácticas en scripts de Node.js
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de todas las páginas de esta sección.
Busca en todas las páginas de la documentación
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de todas las páginas de esta sección.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
typescript-eslint (el meta-paquete) en lugar de separados @typescript-eslint/parser y @typescript-eslint/eslint-plugin para obtener tseslint.config() y el parser incluido conectado consistentemente.no-floating-promises y no-misused-promises no funcionan silenciosamente sin parserOptions.project; establécelo (y tsconfigRootDir: import.meta.dirname) para que las rutas se resuelvan relativas a la configuración, no al cwd.eslint-config-prettier debe ser la entrada final o sus deshabilitaciones de reglas se anulan, y un bloque { ignores: […] } solo actúa como un ignore global cuando es la única clave en su objeto - mezclarlo con rules lo convierte en un filtro por archivo..mjs es siempre ESM, .cjs es siempre CommonJS, y .js simple sigue el campo "type" de package.json más cercano (predeterminado a CommonJS); cambiar "type": "module" convierte cada .js debajo y requiere renombrar retrocesos CJS a .cjs.ERR_MODULE_NOT_FOUND sin la extensión - import { helper } from "./utils.js" incluso cuando la fuente es utils.ts - porque bajo "module": "NodeNext" el especificador modela la ruta runtime emitida.__dirname, __filename, y require no existen en el alcance ESM - const __dirname = path.dirname(fileURLToPath(import.meta.url)) - en lugar de copiar y pegar código CJS que lanza silenciosamente."module": "NodeNext" como "moduleResolution": "NodeNext" para que TypeScript modele fielmente la resolución real de Node - incluyendo condiciones exports, .mts/.cts, y la extensión .js requerida.EventEmitter trata 'error' especialmente - emitiéndolo sin listeners registrados bloquea el proceso con una excepción no capturada, así que adjunta un manejador (incluso solo registrando) en cada emisor que posees.emitter.off(event, fn) solo elimina la referencia exacta de función que registraste - const handler = () => { ... }; emitter.on("data", handler); emitter.off("data", handler) - las funciones flecha anónimas nunca coinciden, así que guarda el manejador en una variable nombrada.MaxListeners predeterminado es 10 y la advertencia se dispara una vez a las 11, que es una señal fácil de perder de fuga; arregla el sobre-registro, aumenta el límite intencionalmente con setMaxListeners(), o usa EventEmitter.defaultMaxListeners.node:http sin límite de bytes es un vector de ataque OOM; acumula en un contador de longitud y responde 413 una vez que cruces un límite (Express usa express.json({ limit }), Fastify tiene bodyLimit).app.get("/", (req, res, next) => { handleAsync(req, res).catch(next) }) - o actualiza a Express 5 que reenvía al middleware de error de forma nativa.res.send/res.json; Fastify envía lo que devuelves desde el manejador - mezclar los dos estilos (devolver datos en Express, llamar a reply.send en Fastify) produce respuestas colgadas o duplicadas.sudo completamente, y te permiten cambiar versiones de Node por proyecto; ejecutar el instalador oficial más sudo npm install -g lleva directamente a miseria EACCES."packageManager": "pnpm@x.y.z" en package.json para que Corepack (incluido con Node 18.17+) aplique la herramienta exacta y la versión en cada colaborador y ejecutor CI, eliminando la deriva de instalación "funciona en mi máquina".node_modules aplanado y hoisted de npm oculta deps fantasma (importaciones no en package.json); la migración al layout estrictamente simbólico de pnpm las suface como errores reales - arréglalo añadiendo las dependencias, no cambiando a node-linker=hoisted.parseArgs integrado maneja opciones sin agregar una dependencia - const { values } = parseArgs({ args: process.argv.slice(2), options: { port: { type: "string", default: "3000" } } }) - y salta automáticamente las entradas de nodo y ruta de script.npm run cli --flag pasa --flag a npm mismo, no a tu script; usa npm run cli -- --flag para que la bandera llegue al comando subyacente (pnpm y yarn se comportan de la misma manera).tsx inicia en ~100ms usando esbuild y es ideal para scripts locales; la producción debe ejecutar JavaScript compilado vía tsc + node para que no haya dependencia de runner y el costo de inicio se mantenga plano.tsx, ts-node, y node --experimental-strip-types todos descartan tipos y entregan JavaScript a V8 - ninguno de ellos captura errores de tipo en tiempo de ejecución, así que ejecuta tsc --noEmit en CI para seguridad real.@types/node como devDependency, process, Buffer, y cada importación node:* son any, matando el autocompletado y ocultando bugs reales detrás de coerciones implícitas-any silenciosas.fs.readFile(path) sin una codificación devuelve un Buffer, no una cadena - const text = await readFile("config.json", "utf8") - así que el análisis de downstream .split/regex/JSON funciona sin un búfer sorpresa.process.exit() termina inmediatamente y trunca stdout pendiente o escrituras async - process.exitCode = 1; return - dejando que el event loop se drene preserva logs mientras aún falla el script.process.stdout.isTTY es false, que es el predeterminado en la mayoría de entornos CI; establece FORCE_COLOR=1 (o equivalente) en el env de CI si quieres salida de log coloreada y recuerda que Chalk v5 es solo ESM.Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥