Conceptos básicos de scripts de Node.js
11 ejemplos para comenzar con scripts de Node.js -- 8 básicos y 3 intermedios.
Requisitos previos
Necesitas Node.js 22 LTS o más reciente. Verifica con node --version; instala vía nvm, fnm, o Volta si no lo tienes.
# Instala o cambia a la LTS actual con nvm
nvm install --lts
nvm use --lts
node --version # debería mostrar v22.x o superiorConvenciones para cada ejemplo siguiente:
- Los archivos viven en una carpeta con un
package.json. Ejecutanpm init -yuna vez para crear uno. - Las fuentes usan Módulos ES (
import/export). Establece"type": "module"enpackage.json. - Los ejemplos de TypeScript se ejecutan con
tsx-- sin paso de compilación necesario durante el desarrollo.
Ejemplos básicos
1. Verifica que Node y npm estén instalados
Un comando simple para confirmar tu toolchain antes de escribir cualquier script.
node --version # v22.15.0 o similar
npm --version # 10.x o similar
node -e "console.log('hello from node', process.version)"node -e "<code>"ejecuta un fragmento en línea -- perfecto para pruebas de humo sin crear un archivo.process.versionreporta la versión de Node en ejecución para que detectes problemas de versión inmediatamente (p. ej., PATH antiguo recogiendo un binario antiguo).- Si
nodefalta, instala víanvm(macOS/Linux) ofnm/Volta(multiplataforma) -- evita la instalación global de Homebrew en máquinas compartidas. - Fija la versión con un archivo
.nvmrc(o.node-version) para que cada colaborador ejecute el mismo runtime.
Relacionado: Instalación de Node.js y npm -- nvm, fnm, Volta y el ciclo LTS
2. Script Hello World en JavaScript
El archivo .js más pequeño y útil que puedes ejecutar con node.
// hello.js
const greeting = "Hello, Node.js!";
console.log(greeting);Ejecútalo:
node hello.js- Cualquier archivo
.jscon código válido se ejecuta directamente bajonode-- sin bundler, sin configuración. - Con
"type": "module"enpackage.json, los archivos.jsse tratan como Módulos ES (import/export). Sin él, son CommonJS (require/module.exports). - Usa
node --watch hello.jsdurante el desarrollo para volver a ejecutar al guardar (Node 18+ incorporado). process.argvcontiene argumentos CLI;process.envcontiene variables de entorno -- ambos siempre están disponibles.
Relacionado: Ejecutar scripts JavaScript -- puntos de entrada, modo watch, líneas shebang | ESM vs CommonJS -- eligiendo el sistema de módulos
3. Ejecuta un script de TypeScript con tsx
Salta el paso de compilación en desarrollo -- tsx ejecuta archivos .ts directamente.
npm install --save-dev tsx typescript @types/node// sum.ts
function sum(a: number, b: number): number {
return a + b;
}
console.log(sum(2, 3));Ejecútalo:
npx tsx sum.tstsxusa esbuild bajo el capó -- inicio rápido y el mismo comportamiento de stripping de tipos que en los builds de producción.- Usa
tsx watch sum.tspara un bucle de desarrollo con recarga activa; usanpx tscsolo cuando quieras producir salida.jspara despliegue. - Agrega
@types/nodepara que los módulos incorporados comofs,pathyprocessestén tipados. - Node 22.6+ incluye stripping de tipos nativo (
node --experimental-strip-types sum.ts) -- un buen fallback cuando no puedes agregartsx.
Relacionado: Ejecutar scripts TypeScript -- tsx vs ts-node vs stripping nativo | ESLint para scripts de Node.js -- conectando lint a un proyecto de scripts TS
4. Instala paquetes con npm o pnpm
Ambas herramientas usan el mismo package.json; pnpm es más rápido y eficiente en disco.
# npm
npm install zod
npm install --save-dev tsx
# pnpm (habilita vía Corepack, no se necesita instalación global)
corepack enable
pnpm add zod
pnpm add -D tsxpackage.jsones la fuente de verdad -- ambas herramientas la leen/escriben. Cambiar entre ellas es seguro si tu equipo acepta una a la vez.- El almacén direccionable por contenido de pnpm deduplica copias entre proyectos -- grandes ahorros de disco y tiempo de instalación en máquinas con muchos repos.
- Confirma exactamente un lockfile:
package-lock.jsonpara npm,pnpm-lock.yamlpara pnpm. Elimina el otro. - Usa
corepackpara fijar la versión exacta del gestor de paquetes enpackageManagerpara que CI y laptops se mantengan alineados.
Relacionado: pnpm vs npm -- workspaces, hoisting y comparación de rendimiento
5. Importaciones ESM
Usa el sistema de módulos moderno -- importaciones estáticas, await de nivel superior, exportaciones con nombre.
// package.json
{
"type": "module"
}// math.ts
export function sum(a: number, b: number) {
return a + b;
}// app.ts
import { sum } from "./math.js";
console.log(sum(2, 3));- Establece
"type": "module"enpackage.jsonpara que los archivos.js/.tssean ESM por defecto. - ESM requiere extensiones de archivo explícitas en imports --
./math.jsincluso cuando la fuente esmath.ts. - Top-level
awaitestá permitido en ESM:const data = await fetch(...)en la parte superior de un archivo funciona. - Mezclar CJS y ESM en el mismo archivo es problemático -- elige uno y mantente con él para el proyecto.
Relacionado: ESM vs CommonJS -- reglas de interoperabilidad,
createRequirey cuándo CJS aún tiene sentido
6. Lee un archivo con fs/promises
Lee y escribe archivos de forma asincrónica -- la API recomendada desde Node 14.
// read-config.js
import { readFile, writeFile } from "node:fs/promises";
const text = await readFile("config.json", "utf8");
const config = JSON.parse(text);
config.apiUrl = config.apiUrl.replace("http:", "https:");
await writeFile("config.json", JSON.stringify(config, null, 2));// read-config.ts
import { readFile, writeFile } from "node:fs/promises";
const text = await readFile("config.json", "utf8");
const config = JSON.parse(text) as { apiUrl: string };
config.apiUrl = config.apiUrl.replace("http:", "https:");
await writeFile("config.json", JSON.stringify(config, null, 2));fs/promisesretorna Promises -- emparéjalo con top-levelawaitpara scripts limpios.- Siempre pasa la codificación (
"utf8"); omítela solo si quieres unBuffer. - Prefija los módulos incorporados con
node:(node:fs,node:path,node:url) -- inequívoco y funciona en ESM y CJS. - Para archivos grandes o trabajo línea por línea, usa streams (
createReadStream) oreadlineen lugar de almacenar todo el archivo en memoria.
Relacionado: Scripts de utilidad -- E/S de archivos, rutas, globs y recetas de scripts
7. Analiza argumentos CLI
Usa parseArgs de node:util -- incorporado, tipado, sin dependencia extra.
// greet.ts
import { parseArgs } from "node:util";
const { values } = parseArgs({
options: {
name: { type: "string", short: "n", default: "world" },
shout: { type: "boolean", short: "s", default: false },
},
});
const greeting = `Hello, ${values.name}!`;
console.log(values.shout ? greeting.toUpperCase() : greeting);Ejecuta:
npx tsx greet.ts --name Ada --shout
# HELLO, ADA!parseArgsestá incorporado en Node 18+ -- no hay necesidad deyargsocommanderpara la mayoría de scripts.short: "n"permite a los llamadores pasar-n Adaen lugar de--name Ada.- Retorna
values(flags analizados) ypositionals(argumentos restantes) -- ambos completamente tipados desde el esquema. - Para CLIs más grandes con subcomandos, texto de ayuda y prompts, evoluciona a
commanderuoclif.
Relacionado: Scripts de utilidad -- patrones CLI, prompts, códigos de salida
8. EventEmitter
Pub/sub dentro de un único proceso -- la base para streams, servidores HTTP y eventos personalizados.
// pubsub.js
import { EventEmitter } from "node:events";
const bus = new EventEmitter();
bus.on("login", (userId) => {
console.log(`User ${userId} logged in`);
});
bus.emit("login", "u_123");
bus.emit("login", "u_456");// pubsub.ts
import { EventEmitter } from "node:events";
interface Events {
login: [userId: string];
logout: [userId: string];
}
const bus = new EventEmitter<Events>();
bus.on("login", (userId) => {
console.log(`User ${userId} logged in`);
});
bus.emit("login", "u_123");
bus.emit("login", "u_456");emit(name, ...args)llama a cada listener registrado conon(name, handler)en orden de registro.- Usa el genérico tipado
EventEmitter<T>(Node 22+) para obtener validación en tiempo de compilación de nombres de eventos y cargas útiles. - Siempre desvincula listeners con
off(name, handler)en procesos de larga ejecución -- los listeners olvidados pierden memoria. onagrega un listener persistente;oncese dispara y se elimina a sí mismo -- elige el correcto para evitar handlers duplicados.
Relacionado: Patrones de EventEmitter -- eventos tipados, fugas, iteración asincrónica
Ejemplos intermedios
9. Servidor HTTP mínimo con node:http
Cero dependencias -- sirve JSON desde el módulo http incorporado.
// server.ts
import { createServer } from "node:http";
const server = createServer((req, res) => {
if (req.url === "/health") {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ ok: true, uptime: process.uptime() }));
return;
}
res.writeHead(404).end("Not Found");
});
const port = Number(process.env.PORT ?? 3000);
server.listen(port, () => {
console.log(`http://localhost:${port}`);
});createServerretorna unEventEmitter-- también puedes escuchar"request","connection","close"directamente.- Llama a
res.writeHeadantes deres.end;res.endcierra la conexión. - Para algo más que un endpoint de salud (enrutamiento, middleware, validación), recurre a Express o Fastify.
- Maneja
process.on("SIGTERM", () => server.close(...))para que los orquestadores de contenedores puedan cerrarte correctamente.
Relacionado: Creación de servidores HTTP -- node:http, Express y Fastify lado a lado
10. Servidor Express con TypeScript
API familiar, ecosistema enorme -- la opción predeterminada para la mayoría de servicios Node.js.
npm install express
npm install --save-dev @types/express// app.js
import express from "express";
const app = express();
app.use(express.json());
app.post("/users", (req, res) => {
const { email } = req.body;
if (!email?.includes("@")) {
return res.status(400).json({ error: "Invalid email" });
}
res.status(201).json({ id: crypto.randomUUID(), email });
});
app.listen(3000, () => console.log("http://localhost:3000"));// app.ts
import express, { type Request, type Response } from "express";
const app = express();
app.use(express.json());
interface CreateUserBody {
email: string;
}
app.post("/users", (req: Request<{}, unknown, CreateUserBody>, res: Response) => {
const { email } = req.body;
if (!email?.includes("@")) {
return res.status(400).json({ error: "Invalid email" });
}
res.status(201).json({ id: crypto.randomUUID(), email });
});
app.listen(3000, () => console.log("http://localhost:3000"));express.json()analiza cuerpos de solicitudapplication/json-- sin él,req.bodyesundefined.- Tipifica los genéricos de
Request(Params,ResBody,ReqBody,Query) para que los handlers estén completamente tipados de extremo a extremo. - Usa
zodovalibotdentro del handler para validar cuerpos en lugar de confiar en el cliente. - Para una alternativa más rápida con validación de esquema incorporada, usa Fastify -- el mismo modelo mental, mejor rendimiento.
Relacionado: Creación de servidores HTTP -- tradeoffs de Express vs Fastify vs node:http | ESLint para scripts de Node.js -- reglas recomendadas para código de servidor
11. Configuración Flat Config de ESLint para scripts de TypeScript
ESLint moderno 9+ usa eslint.config.js (flat config) -- más limpio, rápido y consciente de tipos.
npm install --save-dev \
eslint typescript-eslint @eslint/js globals// eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import globals from "globals";
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: { projectService: true },
globals: { ...globals.node },
},
rules: {
"no-console": ["warn", { allow: ["warn", "error"] }],
},
},
);Ejecuta:
npx eslint .
npx eslint . --fix- Flat config es un único archivo que reemplaza
.eslintrc.*y.eslintignore-- mucho menos que aprender. recommendedTypeCheckedactiva reglas conscientes de tipos (necesitaprojectService: true), que detectananyinseguro y mal uso de promises.globals.nodeenseña a ESLint sobreprocess,Buffer,__dirname, etc., para que no desencadenen "no-undef".- Empareja con un script npm
lintpara que CI pueda fallar rápidamente en errores de estilo y tipo.
Relacionado: ESLint para scripts de Node.js -- recomendaciones de reglas, configuración consciente de tipos, patrones de ignorancia | Configuración de ESLint (Linting & Formatting) -- referencia más amplia de ESLint