Cheatsheet Node.js
Runtime JavaScript server-side
Node.js
Módulos e Setup
CommonJS (require)
// math.js
function sumar(a, b) { return a + b; }
module.exports = { sumar };
// app.js
const { sumar } = require("./math");
console.log(sumar(2, 3)); // 5CommonJS es el sistema de módulos clásico de Node. Usa module.exports para exportar y require() para importar.
Módulos nativos
const fs = require("fs");
const path = require("path");
const http = require("http");
const os = require("os");
const crypto = require("crypto");
console.log(os.platform()); // "win32"
console.log(os.cpus().length); // 8Node incluye módulos nativos (fs, path, http, os, crypto...) que no necesitan instalación. Impórtalos directamente por su nombre.
Variables de entorno
// .env
PORT=3000
DB_URL=mongodb://localhost/app
// app.js
require("dotenv").config();
const puerto = process.env.PORT || 3000;
// Node 20+ (sin dotenv):
// node --env-file=.env app.jsLas variables de entorno guardan configuraciones fuera del código. dotenv carga el fichero .env en process.env; en Node 20+ usa --env-file.
ES Modules (import)
// package.json
{ "type": "module" }
// math.js
export function sumar(a, b) {
return a + b;
}
// app.js
import { sumar } from "./math.js";Los ES Modules son el estándar moderno. Actívalos con "type": "module" en package.json. La extensión .js es obligatoria en el import.
Import dinámico
// Carga solo cuando es necesario
const módulo = await import("./pesado.js");
// Condicional
if (process.env.DEBUG) {
const { log } = await import("./debug.js");
log("modo debug");
}El import() dinámico carga módulos bajo demanda y devuelve una Promise. Útil para code-splitting y dependencias opcionales.
Argumentos CLI
// node app.js --nombre=Ana --edad=30
const args = process.argv.slice(2);
// Parse simple:
const params = Object.fromEntries(
args.map(a => a.replace("--", "").split("="))
);
console.log(params.nombre); // "Ana"process.argv contiene los argumentos de la línea de comandos. Los dos primeros son node y el fichero — usa slice(2) para ignorarlos.
Exports: varias formas
// Varias funciones:
module.exports = { sumar, restar, PI };
// Una clase:
module.exports = class Servidor { ... };
// Añadir al exports existente:
exports.nombre = "app";
exports.versión = "1.0";module.exports es el objeto devuelto por require. exports es un atajo — pero reasignar exports = {} no funciona (usa module.exports).
__dirname y __filename
// CommonJS
console.log(__dirname); // carpeta del fichero
console.log(__filename); // ruta completa
const path = require("path");
const config = path.join(__dirname, "config.json");En CommonJS, __dirname es la carpeta del fichero actual y __filename la ruta completa. Esenciales para construir rutas absolutas.
Eventos (EventEmitter)
const EventEmitter = require("events");
const emissor = new EventEmitter();
emissor.on("datos", (d) => {
console.log("Recebido:", d);
});
emissor.once("inicio", () => console.log("1x"));
emissor.emit("datos", { id: 1 });El EventEmitter es la base del patrón de eventos de Node. on registra un listener, emit lo dispara y once corre solo una vez.
Default vs Nombrado
// math.js (ESM)
export default function sumar(a, b) {
return a + b;
}
export const PI = 3.14;
// app.js
import sumar, { PI } from "./math.js";Un módulo puede tener un export default (importado sin llaves) y varios exports nombrados (con llaves). Combina ambos en el mismo import.
import.meta (ESM)
// ES Modules (no tiene __dirname)
import { fileURLToPath } from "url";
import path from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);En los ES Modules no existe __dirname. Usa import.meta.url con fileURLToPath para obtener la ruta del fichero.
Sistema de Ficheiros
Leer fichero
const fs = require("fs/promises");
// Asíncrono (recomendado)
const contenido = await fs.readFile(
"datos.txt", "utf-8"
);
// Síncrono (bloquea el event loop)
const fsSync = require("fs");
const txt = fsSync.readFileSync("datos.txt", "utf-8");Usa fs/promises con await para no bloquear. readFileSync solo es aceptable en scripts de arranque o CLI.
Listar y verificar
const fs = require("fs/promises");
// Listar directorio
const ítems = await fs.readdir("./src");
// Con el tipo de fichero:
const comTipo = await fs.readdir("./src", {
withFileTypes: true,
});
comTipo.filter(e => e.isFile());
// Verificar existencia
await fs.access("fichero.txt"); // lanza error si no existereaddir lista el contenido de una carpeta. Con withFileTypes obtienes el tipo de cada entrada. access verifica si existe (lanza error en caso contrario).
Borrar y renombrar
const fs = require("fs/promises");
// Borrar fichero
await fs.unlink("temp.txt");
// Borrar carpeta (con contenido)
await fs.rm("carpeta", { recursive: true });
// Renombrar / mover
await fs.rename("antiguo.txt", "nuevo.txt");unlink borra ficheros. rm con recursive borra carpetas enteras. rename también sirve para mover.
Escribir fichero
const fs = require("fs/promises");
// Sobrescribir (crea si no existe)
await fs.writeFile("log.txt", "Hola");
// Añadir al final
await fs.appendFile("log.txt", "\nNova linea");
// Crear carpeta (con subcarpetas)
await fs.mkdir("carpeta/sub", { recursive: true });writeFile sobrescribe el contenido y appendFile añade al final. mkdir con recursive crea toda la jerarquía.
Módulo path
const path = require("path");
path.join(__dirname, "src", "app.js")
path.basename("/a/b/f.txt") // "f.txt"
path.extname("foto.png") // ".png"
path.resolve("./config") // ruta absoluta
path.dirname("/a/b/f.txt") // "/a/b"path manipula rutas de forma segura en cualquier SO. join concatena con el separador correcto (/ o \).
Información (stat)
const fs = require("fs/promises");
const info = await fs.stat("fichero.txt");
info.size // tamaño en bytes
info.isFile() // true
info.isDirectory() // false
info.mtime // última modificación
info.birthtime // creaciónstat devuelve metadatos del fichero: tamaño, tipo y fechas. Útil para verificar antes de procesar o para mostrar al usuario.
Copiar ficheros
const fs = require("fs/promises");
// Fichero único
await fs.copyFile("origem.txt", "destino.txt");
// Carpeta entera (Node 16.7+)
await fs.cp("carpeta-src", "carpeta-dst", {
recursive: true,
});copyFile copia un fichero. cp con recursive copia carpetas enteras — útil para backups y plantillas.
Streams (ficheros grandes)
const fs = require("fs");
const lectura = fs.createReadStream("grande.csv");
const escritura = fs.createWriteStream("copia.csv");
lectura.pipe(escritura);
lectura.on("data", (chunk) => {
console.log("Lido:", chunk.length, "bytes");
});
lectura.on("end", () => console.log("Fin"));Los streams procesan ficheros en bloques (chunks) sin cargar todo en la memoria. pipe conecta la lectura a la escritura automáticamente.
Observar cambios (watch)
const fs = require("fs");
const watcher = fs.watch("./src", (evento, fichero) => {
console.log(evento, fichero); // "change" "app.js"
});
// Dejar de observar
watcher.close();fs.watch notifica cuando los ficheros cambian (crear, modificar, borrar). Útil para build tools y live reload. Llama a close() para parar.
Leer y escribir JSON
const fs = require("fs/promises");
// Leer
const texto = await fs.readFile("config.json", "utf-8");
const config = JSON.parse(texto);
// Escribir (formateado)
await fs.writeFile(
"config.json",
JSON.stringify(config, null, 2)
);Combina readFile/writeFile con JSON.parse y JSON.stringify. El tercer argumento (2) formatea con indentación.
Stream pipeline
const { pipeline } = require("stream/promises");
const fs = require("fs");
const zlib = require("zlib");
await pipeline(
fs.createReadStream("datos.csv"),
zlib.createGzip(),
fs.createWriteStream("datos.csv.gz")
);pipeline encadena streams y propaga errores correctamente (a diferencia del pipe simple). Ideal para transformar y comprimir datos.
Assincronismo
Promises
function esperar(ms) {
return new Promise((resolve) =>
setTimeout(resolve, ms)
);
}
esperar(1000).then(() => {
console.log("1s depois");
});
// Rechazar:
new Promise((_, reject) =>
reject(new Error("falhou"))
);Una Promise representa un valor futuro. resolve indica éxito y reject indica error. Se consume con .then().
setTimeout / setInterval
// Una vez tras 2s
setTimeout(() => {
console.log("atrasado");
}, 2000);
// Repetir cada 1s
const id = setInterval(() => {
console.log("tick");
}, 1000);
clearInterval(id); // parar
clearTimeout(id); // cancelar timeoutsetTimeout ejecuta una vez tras un retraso y setInterval repite periódicamente. Guarda el ID para cancelar con clear.
AbortController
const controller = new AbortController();
// Cancelar tras 3s
const timeout = setTimeout(
() => controller.abort(), 3000
);
try {
const resp = await fetch(url, {
signal: controller.signal,
});
} catch (e) {
console.log("Cancelado:", e.name); // "AbortError"
}AbortController cancela operaciones asíncronas (como fetch). Pasa el signal y llama a abort() para interrumpir.
async / await
async function fetchData() {
try {
const resp = await fetch(url);
const datos = await resp.json();
return datos;
} catch (error) {
console.error("Fallo:", error.message);
throw error;
}
}async/await hace el código asíncrono legible como síncrono. await pausa hasta que la Promise resuelva. try/catch maneja los errores.
setImmediate / nextTick
console.log("1 - síncrono");
process.nextTick(() => console.log("2 - nextTick"));
Promise.resolve().then(() => console.log("3 - microtask"));
setImmediate(() => console.log("4 - immediate"));
setTimeout(() => console.log("5 - timer"), 0);
// Orden típico: 1, 2, 3, 4/5process.nextTick corre antes de las microtasks. setImmediate se ejecuta en la fase de check del event loop. Ambos aplazan código sin usar timers.
Concurrencia limitada
async function mapLimit(ítems, limite, fn) {
const resultados = [];
for (let i = 0; i < ítems.length; i += limite) {
const lote = ítems.slice(i, i + limite);
resultados.push(...await Promise.all(lote.map(fn)));
}
return resultados;
}
await mapLimit(urls, 5, (u) => fetch(u));Para no abrir cientos de peticiones en paralelo, procesa en lotes (Promise.all por grupo). Controla el consumo de memoria y sockets.
Promise.all / allSettled
// Todas en paralelo (falla si una falla)
const [a, b, c] = await Promise.all([
fetch("/api/a"),
fetch("/api/b"),
fetch("/api/c"),
]);
// Todas (incluso con errores)
const resultados = await Promise.allSettled(promessas);
resultados.filter(r => r.status === "fulfilled");Promise.all corre en paralelo pero rechaza si una falla. allSettled espera todas y devuelve el estado de cada una.
Promisify (callbacks)
const { promisify } = require("util");
const fs = require("fs");
// Convertir callback → Promise
const leer = promisify(fs.readFile);
const contenido = await leer("datos.txt", "utf-8");
// Hoy se prefiere fs/promises:
const fsp = require("fs/promises");util.promisify convierte funciones de callback (estilo (err, resultado)) en Promises. Hoy, prefiere los módulos */promises nativos.
Manejar errores (patrón)
// Wrapper para evitar try/catch repetido
async function capturar(promise) {
try {
const datos = await promise;
return [datos, null];
} catch (error) {
return [null, error];
}
}
const [datos, error] = await capturar(fetch(url));
if (error) console.error(error);Este patrón [datos, error] (inspirado en Go) evita try/catch anidado. Verifica si hay error antes de usar los datos.
Promise.race / any
// Primera en resolver O rechazar
const maisRapida = await Promise.race([
fetch("/api/lento"),
esperar(5000).then(() => "timeout"),
]);
// Primera en resolver (ignora errores)
const primera = await Promise.any([
fetch("/api/servidor1"),
fetch("/api/servidor2"),
]);race devuelve el primer resultado (éxito o error). any devuelve el primer éxito, ignorando rechazos. Útil para timeouts.
Async iterators (for await)
const fs = require("fs");
// Leer stream bloque a bloque
const stream = fs.createReadStream("grande.txt");
for await (const chunk of stream) {
console.log("Bloco:", chunk.length);
}for await...of recorre fuentes asíncronas (streams, async generators) de forma secuencial. Consume cada bloque a medida que llega.
Event Loop (concepto)
console.log("1 - síncrono");
setTimeout(() => console.log("3 - timer"), 0);
Promise.resolve().then(
() => console.log("2 - microtask")
);
// Orden: 1, 2, 3
// Síncrono → Microtasks → TimersEl Event Loop procesa: código síncrono → microtasks (Promises) → timers (setTimeout). Incluso con delay 0, el timer corre en último lugar.
HTTP e Servidor
Servidor HTTP nativo
const http = require("http");
const servidor = http.createServer((req, res) => {
res.writeHead(200, {
"Content-Type": "text/plain",
});
res.end("Hola do Node!");
});
servidor.listen(3000, () => {
console.log("Em http://localhost:3000");
});El módulo http crea un servidor sin dependencias. El callback recibe la petición (req) y la respuesta (res). listen lo inicia en el puerto.
Servir ficheros estáticos
const http = require("http");
const fs = require("fs");
const path = require("path");
http.createServer((req, res) => {
const fichero = path.join("./public", req.url);
fs.readFile(fichero, (error, datos) => {
if (error) {
res.writeHead(404);
return res.end("No encontrado");
}
res.end(datos);
});
}).listen(3000);Un servidor de ficheros estáticos lee del disco y lo devuelve al cliente. En producción, usa Express (express.static) o un CDN.
Redirecciones
// 301 (permanente)
res.writeHead(301, { Location: "/nueva-pagina" });
res.end();
// 302 (temporal)
res.writeHead(302, { Location: "/login" });
res.end();El 301 es permanente (el SEO se transfiere a la nueva URL). El 302 es temporal. El browser sigue el header Location automáticamente.
fetch (Node 18+)
// GET
const resp = await fetch("https://api.ejemplo.com/datos");
const datos = await resp.json();
// POST
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ nombre: "Ana" }),
});Desde Node 18, fetch es nativo (sin instalar nada). Funciona como en el browser: resp.json() para leer el cuerpo.
URL y query params
const url = new URL(
"http://site.com/búsqueda?q=node&p=2"
);
url.pathname // "/búsqueda"
url.searchParams.get("q") // "node"
url.searchParams.get("p") // "2"
// Construir URL:
const u = new URL("http://api.com/datos");
u.searchParams.set("page", "1");La clase URL analiza y construye URLs. searchParams da acceso fácil a los parámetros de query string.
HTTPS / TLS
const https = require("https");
const fs = require("fs");
const opciones = {
key: fs.readFileSync("clave.pem"),
cert: fs.readFileSync("cert.pem"),
};
https.createServer(opciones, (req, res) => {
res.end("conexión segura");
}).listen(443);El módulo https crea un servidor encriptado. Necesita una clave (key) y certificado (cert). En producción, normalmente se usa un reverse proxy.
fetch con timeout
const resp = await fetch(url, {
signal: AbortSignal.timeout(5000), // 5s
});
if (!resp.ok) {
throw new Error("HTTP " + resp.status);
}
const datos = await resp.json();AbortSignal.timeout() cancela el fetch tras un límite. Verifica siempre resp.ok — fetch no lanza error en status 4xx/5xx.
Headers y status
// Leer headers de la petición:
req.headers["content-type"]
req.headers["authorization"]
// Definir en la respuesta:
res.writeHead(200, {
"Content-Type": "application/json",
"Cache-Control": "no-cache",
"X-Custom": "valor",
});Los headers transportan metadatos (tipo de contenido, autenticación, caché). Léelos en req.headers y defínelos con writeHead.
Leer cuerpo de la petición (POST)
http.createServer((req, res) => {
let corpo = "";
req.on("data", (chunk) => { corpo += chunk; });
req.on("end", () => {
const datos = JSON.parse(corpo);
res.end("Recebido: " + datos.nombre);
});
}).listen(3000);En el servidor nativo, el cuerpo llega en bloques vía eventos data. Acumúlalos y procésalos en el evento end. Express simplifica esto con express.json().
Cookies
// Definir cookie en la respuesta:
res.writeHead(200, {
"Set-Cookie": "sesión=abc123; HttpOnly; Path=/",
});
// Leer cookies de la petición:
const cookies = req.headers.cookie; // "sesión=abc123"Las cookies se definen con el header Set-Cookie y se leen en req.headers.cookie. El flag HttpOnly impide el acceso vía JavaScript.
Express.js
Setup Express
npm install express
const express = require("express");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.listen(3000, () => {
console.log("Servidor activo na puerto 3000");
});Express es el framework web más popular de Node. express.json() parsea el cuerpo JSON automáticamente.
Middleware
// Logger personalizado
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // pasar al siguiente
});
// Middleware de error (4 argumentos)
app.use((error, req, res, next) => {
console.error(error.stack);
res.status(500).json({ error: error.message });
});El middleware corre entre la petición y la ruta. next() pasa al siguiente. El handler de error tiene 4 argumentos y debe ser el último.
Validación con middleware
function validarId(req, res, next) {
const id = Number(req.params.id);
if (isNaN(id)) {
return res.status(400).json({
error: "ID debe ser numérico",
});
}
req.id = id;
next();
}
app.get("/users/:id", validarId, (req, res) => {
res.json({ id: req.id });
});Un middleware de validación verifica los datos antes de la ruta. Si falla, responde con un error y no llama a next().
Rutas y parámetros
app.get("/", (req, res) => {
res.send("Inicio");
});
// Parámetro de ruta
app.get("/users/:id", (req, res) => {
res.json({ id: req.params.id });
});
// Query string ?búsqueda=x
app.get("/búsqueda", (req, res) => {
res.json({ q: req.query.búsqueda });
});Las rutas mapean método + ruta. Los :params capturan segmentos de la URL. req.query lee la query string.
Encadenar handlers
function validar(req, res, next) {
if (!req.body.nombre) return next(new Error("nombre"));
next();
}
function autenticar(req, res, next) {
if (!req.user) return res.status(401).end();
next();
}
app.post("/datos", autenticar, validar, (req, res) => {
res.json({ ok: true });
});Puedes pasar varios handlers en una ruta — corren en secuencia. Cada uno llama a next() para avanzar o responde/lanza error para parar.
Subida de ficheros (multer)
npm install multer
const multer = require("multer");
const upload = multer({ dest: "uploads/" });
app.post("/upload", upload.single("fichero"),
(req, res) => {
console.log(req.file.originalname);
console.log(req.file.path);
res.json({ ok: true });
}
);multer procesa subidas multipart. single() acepta un fichero. Los datos quedan en req.file (nombre, ruta, tamaño).
Objeto req (petición)
app.post("/users/:id", (req, res) => {
req.params.id // parámetro de la ruta
req.query.activo // query string
req.body // cuerpo (con express.json)
req.headers // cabeceras
req.method // "POST"
req.path // "/users/5"
req.ip // IP del cliente
});El objeto req trae todo sobre la petición: params, query, body, headers, method e ip.
Respuestas (res)
res.send("texto HTML");
res.json({ ok: true });
res.status(201).json({ id: 1 });
res.redirect("/login");
res.download("/fichero.pdf");
res.sendFile(path.join(__dirname, "index.html"));
res.status(204).end(); // sin cuerpoEl objeto res tiene métodos para cada tipo de respuesta. json() define el Content-Type automáticamente. El 204 es "éxito sin contenido".
app.set y configuración
app.set("port", process.env.PORT || 3000);
app.set("view engine", "ejs");
// Leer configuración
const puerto = app.get("port");
// Activar/desactivar opciones
app.enable("trust proxy");
app.disable("x-powered-by");
app.listen(app.get("port"));app.set guarda pares clave-valor de configuración y app.get("clave") los lee. enable/disable activan flags booleanos.
Router modular
// routes/productos.js
const router = express.Router();
router.get("/", listar);
router.post("/", crear);
router.get("/:id", ver);
router.put("/:id", actualizar);
router.delete("/:id", eliminar);
module.exports = router;
// app.js
app.use("/productos", require("./routes/productos"));El Router organiza las rutas en ficheros separados. Cada módulo trata un recurso. El prefijo se define en app.use.
Ficheros estáticos
// Servir la carpeta public/
app.use(express.static("public"));
// Con prefijo
app.use("/assets", express.static("public/assets"));
// Acceder: http://localhost:3000/img/logo.png
// O: http://localhost:3000/assets/style.cssexpress.static sirve ficheros directamente (CSS, JS, imágenes). Sin prefijo, el fichero se accede desde la raíz; con prefijo, queda bajo esa ruta.
404 y handler de error
// 404: ninguna ruta coincidió (al final)
app.use((req, res) => {
res.status(404).json({ error: "No encontrado" });
});
// Handler de error (4 args, después del 404)
app.use((error, req, res, next) => {
res.status(error.status || 500).json({
error: error.message,
});
});Un middleware final sin ruta captura rutas inexistentes (404). El handler de error (4 args) centraliza los fallos. Ambos deben estar al final.
API REST e Auth
CRUD completo
let productos = [];
app.get("/productos", (req, res) =>
res.json(productos));
app.post("/productos", (req, res) => {
const nuevo = { id: Date.now(), ...req.body };
productos.push(nuevo);
res.status(201).json(nuevo);
});
app.delete("/productos/:id", (req, res) => {
productos = productos.filter(p => p.id != req.params.id);
res.status(204).end();
});Un CRUD usa GET (listar), POST (crear, 201), PUT (actualizar) y DELETE (204). Los datos vienen en req.body.
Tratamiento de errores
class ApiError extends Error {
constructor(status, mensaje) {
super(mensaje);
this.status = status;
}
}
// En la ruta:
throw new ApiError(404, "Producto no encontrado");
// Handler global (último middleware):
app.use((error, req, res, next) => {
const status = error.status || 500;
res.status(status).json({ error: error.message });
});Crea una clase ApiError con el status HTTP. El handler global centraliza todas las respuestas de error en un solo lugar.
Hash de contraseñas (bcrypt)
npm install bcrypt
const bcrypt = require("bcrypt");
// Registro: guardar el hash (nunca la contraseña en texto)
const hash = await bcrypt.hash(contrasena, 10);
// Login: comparar
const ok = await bcrypt.compare(contrasena, hash);
if (!ok) return res.status(401).end();Nunca guardes contraseñas en texto plano. bcrypt.hash genera un hash con salt (coste 10) y compare lo verifica en el login.
Códigos de estado HTTP
res.status(200) // OK res.status(201) // Creado res.status(204) // Éxito, sem contenido res.status(400) // Pedido inválido res.status(401) // No autenticado res.status(403) // Sem permiso res.status(404) // No encontrado res.status(500) // Error do servidor
Los códigos comunican el resultado: 2xx éxito, 4xx error del cliente, 5xx error del servidor. Úsalos correctamente en vez de devolver siempre 200.
CORS
npm install cors
const cors = require("cors");
// Permitir todo (desarrollo)
app.use(cors());
// Configuración específica (producción)
app.use(cors({
origin: "http://localhost:5173",
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true,
}));CORS controla qué dominios pueden acceder a la API. En producción, especifica el origin exacto en vez de permitir todo.
Paginación
app.get("/productos", (req, res) => {
const pagina = parseInt(req.query.page) || 1;
const limite = parseInt(req.query.limit) || 10;
const inicio = (pagina - 1) * limite;
const resultados = productos.slice(inicio, inicio + limite);
res.json({
datos: resultados,
total: productos.length,
pagina,
paginas: Math.ceil(productos.length / limite),
});
});La paginación devuelve solo una porción de los datos. El cliente envía page y limit. La respuesta incluye el total para el frontend.
Validación de input
function validarProduto(req, res, next) {
const { nombre, precio } = req.body;
if (!nombre || nombre.length < 3) {
return res.status(400).json({
error: "Nombre debe ter 3+ caracteres",
});
}
if (typeof precio !== "number" || precio < 0) {
return res.status(400).json({ error: "Precio inválido" });
}
next();
}
app.post("/productos", validarProduto, crear);Valida siempre los datos del cliente. Verifica tipo, tamaño y formato. Responde con 400 y un mensaje claro si algo falla.
Rate limiting
npm install express-rate-limit
const limitador = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100, // 100 peticiones por IP
message: { error: "Muitos pedidos" },
});
app.use("/api/", limitador);El rate limiting protege contra abuso y DDoS. Limita el numero de peticiones por IP en una ventana de tiempo (windowMs).
Versionado de API
// Por prefijo en la URL (más común)
app.use("/api/v1/productos", routerV1);
app.use("/api/v2/productos", routerV2);
// O por Router
const v1 = express.Router();
v1.get("/productos", listarV1);
app.use("/api/v1", v1);Versionar la API (ej.: /api/v1) permite evolucionar sin romper clientes antiguos. Cada versión tiene su propio router.
Validar con Zod
npm install zod
const { z } = require("zod");
const schema = z.object({
nombre: z.string().min(3),
precio: z.number().positive(),
});
app.post("/productos", (req, res) => {
const resultado = schema.safeParse(req.body);
if (!resultado.success) {
return res.status(400).json(resultado.error.issues);
}
// resultado.data está validado y tipado
});Zod valida y transforma datos con esquemas declarativos. safeParse no lanza error — devuelve success y los issues.
Autenticación JWT
npm install jsonwebtoken
const jwt = require("jsonwebtoken");
// Generar token:
const token = jwt.sign(
{ id: user.id }, "secreto", { expiresIn: "1h" }
);
// Verificar (middleware):
function auth(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
try {
req.user = jwt.verify(token, "secreto");
next();
} catch {
res.status(401).json({ error: "No autorizado" });
}
}El JWT autentica sin sesión. El token va en el header Authorization: Bearer .... El middleware lo verifica e inyecta el usuario en req.user.
Logs de requests (morgan)
npm install morgan
const morgan = require("morgan");
// Formato predefinido
app.use(morgan("dev")); // colorido para dev
app.use(morgan("combined")); // formato Apache
// Salida personalizada
app.use(morgan(":method :url :status :response-time ms"));morgan registra cada petición HTTP (método, URL, status, tiempo). El formato dev es colorido; combined sirve para producción.
NPM e Pacotes
Iniciar proyecto
npm init -y // crear package.json npm install // instalar dependencias npm install express // dependencia de producción npm install -D nodemon // dependencia de dev npm install -g pm2 // global (CLI)
npm init -y crea el package.json con valores por defecto. -D lo guarda en devDependencies (solo para desarrollo).
package-lock.json
// El lock registra las versiones EXACTAS de todo
// (incluyendo dependencias transitivas)
npm ci // instalación limpia basada en el lock
// (más rápido, reproducible)
// Reglas:
// - Nunca editar manualmente
// - Siempre commitear a git
// - Usar npm ci en CI/CDEl package-lock.json garantiza que todos instalan exactamente las mismas versiones. npm ci es ideal para servidores y CI.
engines y tipo de módulo
// package.json
{
"type": "module", // usar ES Modules
"engines": {
"node": ">=20.0.0" // versión mínima
}
}
// "type": "commonjs" es o patrón (require)"type": "module" activa los ES Modules en ficheros .js. engines documenta la versión mínima de Node necesaria.
Eliminar y listar paquetes
npm uninstall paquete // remover npm uninstall -D nodemon // remover de dev npm ls // árbol de dependencias npm ls --depth=0 // solo diretas npm outdated // versiones desatualizadas npm audit // vulnerabilidades
uninstall elimina un paquete y actualiza el package.json. npm ls muestra el árbol y audit detecta vulnerabilidades conocidas.
npx
// Ejecutar sin instalar globalmente npx create-react-app mi-app npx nodemon app.js npx jest --watch // Versión específica npx node@18 -v // Paquetes locales de node_modules/.bin npx eslint src/
npx ejecuta paquetes sin instalarlos globalmente. Si el paquete existe en node_modules, lo usa; si no, lo descarga temporalmente.
.npmrc y registries
# .npmrc (proyecto o ~/.npmrc)
registry=https://registry.npmjs.org/
@mi-empresa:registry=https://npm.empresa.com/
//npm.empresa.com/:_authToken=${NPM_TOKEN}
# Guardar token con login:
npm login --registry=https://npm.empresa.com/El .npmrc configura el registry y los tokens de autenticación. Permite usar registries privados para paquetes con scope (@empresa).
Scripts del package.json
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"test": "jest",
"lint": "eslint src/"
}
}
// Ejecutar:
npm run dev
npm start // "start" no precisa de "run"
npm testLos scripts automatizan comandos. npm run nombre ejecuta cualquier script. start y test funcionan sin run.
Workspaces (monorepo)
// package.json (raíz)
{
"workspaces": ["packages/*"]
}
// Estructura:
// packages/api/package.json
// packages/frontend/package.json
// packages/shared/package.json
npm install // instala todo
npm run dev -w api // corre en un workspaceLos workspaces gestionan múltiples paquetes en un solo repositorio. Las dependencias compartidas se instalan una vez en la raíz.
Publicar paquete
// package.json mínimo:
{
"name": "mi-paquete",
"version": "1.0.0",
"main": "index.js"
}
npm login
npm publish
// Actualizar versión:
npm version patch // 1.0.0 → 1.0.1
npm version minor // 1.0.1 → 1.1.0
npm version major // 1.1.0 → 2.0.0Para publicar, necesitas una cuenta en npmjs.com. npm version actualiza la versión y crea un commit/tag automáticamente.
Gestión de versiones (semver)
npm install paquete // última versión npm install paquete@1.2.3 // versión exata npm install paquete@^1.2.0 // compatible 1.x.x npm install paquete@~1.2.0 // solo patches 1.2.x npm outdated // ver desatualizados npm update // actualizar dentro do range
En semver: ^ acepta minor+patch, ~ solo patch. outdated muestra lo que está viejo y update respeta los rangos.
bin y CLI de paquete
// package.json
{
"name": "mi-cli",
"bin": { "mi-cli": "./cli.js" }
}
// cli.js (primera línea)
#!/usr/bin/env node
console.log("Hola da CLI!");
// npm link → hace el comando disponibleEl campo bin registra los comandos ejecutables del paquete. El shebang #!/usr/bin/env node indica el intérprete. npm link lo instala localmente para pruebas.
CLI, Testes e Boas Práticas
Modo watch (Node 18+)
// Reiniciar al guardar fichero node --watch app.js // Con variables de entorno node --watch --env-file=.env app.js // Ver versiones node -v npm -v
El --watch reinicia el servidor automáticamente cuando guardas el fichero. Sustituye a nodemon en proyectos simples.
Estructura recomendada
proyecto/
src/
routes/ // rotas por recurso
controllers/ // lógica dos handlers
services/ // regra de negocio
models/ // acesso a datos
middleware/ // validación, auth
app.js // configuración Express
package.json
.env
.gitignoreSepara por responsabilidad: routes definen URLs, controllers orquestan, services contienen la lógica. Cada fichero hace una sola cosa.
Tests de API (supertest)
npm install -D supertest vitest
const request = require("supertest");
it("cria producto", async () => {
const resp = await request(app)
.post("/productos")
.send({ nombre: "Teclado", precio: 50 });
expect(resp.status).toBe(201);
expect(resp.body.id).toBeDefined();
});supertest prueba endpoints HTTP sin levantar el servidor. Pasa el app de Express y simula peticiones con get/post y send.
Debugging
// Inspeccionar con Chrome DevTools
node --inspect app.js
node --inspect-brk app.js // pausa no inicio
// Logs útiles
console.log("info");
console.warn("aviso");
console.error("error");
console.table(arrayDeObjetos);
console.time("op");
console.timeEnd("op"); // "op: 123ms"--inspect abre Chrome DevTools para depurar. console.table muestra arrays/objetos en tabla. time/timeEnd mide la duración.
Buenas prácticas
// Usar async/await (no callbacks) // Siempre tratar errores (try/catch) // Config en variables de entorno // Módulos pequeños y enfocados // ESLint + Prettier para consistencia // Nunca commitear node_modules // Usar .gitignore y .env.example
Convenciones que mantienen el código Node limpio y listo para producción. .env.example documenta las variables necesarias sin exponer secretos.
Seguridad básica (helmet)
npm install helmet
const helmet = require("helmet");
app.use(helmet()); // headers de seguridad
// Nunca exponer stack traces en producción:
app.use((error, req, res, next) => {
res.status(500).json({
error: process.env.NODE_ENV === "production"
? "Error interno"
: error.message,
});
});helmet añade headers de seguridad automáticamente. En producción, nunca expongas stack traces — muestra solo "Error interno".
Process y cierre
// Cerrar de forma graciosa (Ctrl+C)
process.on("SIGINT", () => {
console.log("A encerrar...");
servidor.close(() => process.exit(0));
});
// Error no capturado
process.on("uncaughtException", (e) => {
console.error("Fatal:", e);
process.exit(1);
});
// Promise rechazada sin catch
process.on("unhandledRejection", (r) => {
console.error("Rejeitada:", r);
});Maneja SIGINT para cerrar sin perder datos. uncaughtException es el último recurso — registra el error y sale.
ESLint y Prettier
npm install -D eslint prettier // eslint.config.js (flat config) npx eslint --init // Formatear al guardar (VS Code) // "editor.formatOnSave": true // "editor.defaultFormatter": "esbenp.prettier-vscode" npx eslint src/ --fix
ESLint detecta errores y problemas de estilo; Prettier formatea el código automáticamente. Úsalos juntos para consistencia.
Performance y profiling
// Generar perfil de CPU
node --prof app.js
node --prof-process isolate-*.log > perfil.txt
// Heap snapshot (memoria)
node --inspect app.js
// → DevTools → Memory → Take snapshot
// Monitorizar el event loop
const { monitorEventLoopDelay } = require("perf_hooks");--prof genera perfiles de CPU y --inspect permite heap snapshots en DevTools. Esenciales para encontrar bottlenecks y fugas de memoria.
NODE_ENV y configuración
const env = process.env.NODE_ENV || "development";
const config = {
development: { debug: true, dbUrl: "localhost" },
production: { debug: false, dbUrl: process.env.DB_URL },
}[env];
// Arrancar:
// NODE_ENV=production node app.jsNODE_ENV distingue entornos (development/production). Ajusta comportamiento como debug, logging y conexiones a la base de datos.
Tests (Vitest / Jest)
npm install -D vitest
// math.test.js
import { describe, it, expect } from "vitest";
import { sumar } from "./math.js";
describe("sumar", () => {
it("suma dois numeros", () => {
expect(sumar(2, 3)).toBe(5);
});
});
// npx vitestVitest (o Jest) prueba funciones de forma aislada. describe agrupa tests y expect verifica resultados. Se ejecuta con npx vitest.
Avançado
Buffers (datos binarios)
const buf = Buffer.from("Hola", "utf-8");
buf.length // 4 bytes
buf.toString("utf-8") // "Hola"
buf.toString("base64") // "T2zDoA=="
// Reservar y escribir
const b = Buffer.alloc(8);
b.writeUInt32BE(1234, 0);
Buffer.concat([buf, b]);El Buffer representa datos binarios en bruto (fuera del string). Esencial para ficheros, red y encriptación. Convierte con toString(encoding).
Crypto: Hash
const crypto = require("crypto");
const hash = crypto
.createHash("sha256")
.update("texto secreto")
.digest("hex");
// MD5 (no usar para seguridad)
crypto.createHash("md5").update("x").digest("hex");crypto.createHash genera hashes unidireccionales (sha256, md5). Usa SHA-256 para integridad; para contraseñas prefiere bcrypt.
Socket.IO
npm install socket.io
const io = require("socket.io")(servidorHttp);
io.on("connection", (socket) => {
socket.on("chat", (msg) => {
io.emit("chat", msg); // enviar a todos
});
socket.join("sala1");
io.to("sala1").emit("nueva", "datos");
});Socket.IO añade salas, eventos y fallback automático sobre WebSockets. emit envía a todos; to(sala) restringe a un grupo.
Child Processes
const { exec, spawn } = require("child_process");
// exec: salida en buffer (comandos cortos)
exec("ls -la", (error, stdout) => {
console.log(stdout);
});
// spawn: stream (salida larga / tiempo real)
const proc = spawn("node", ["-v"]);
proc.stdout.on("data", (d) => console.log(d.toString()));El módulo child_process ejecuta comandos externos. exec devuelve la salida completa; spawn la transmite en stream (mejor para procesos anchos).
Crypto: HMAC y tokens
const crypto = require("crypto");
// HMAC (hash con clave secreta)
const hmac = crypto
.createHmac("sha256", "secreto")
.update("datos")
.digest("hex");
// Token aleatorio seguro
const token = crypto.randomBytes(32).toString("hex");createHmac genera un hash autenticado con una clave (verifica integridad + origen). randomBytes crea tokens criptográficamente seguros.
Módulo os (sistema)
const os = require("os");
os.platform() // "win32" | "linux" | "darwin"
os.arch() // "x64" | "arm64"
os.cpus().length // núcleos de CPU
os.totalmem() // RAM total (bytes)
os.freemem() // RAM libre
os.homedir() // carpeta do usuario
os.tmpdir() // carpeta temporalEl módulo os da información del sistema operativo: plataforma, arquitectura, CPU y memoria. Útil para adaptar el comportamiento al entorno.
Cluster (multi-core)
const cluster = require("cluster");
const os = require("os");
if (cluster.isPrimary) {
const numCpus = os.cpus().length;
for (let i = 0; i < numCpus; i++) {
cluster.fork(); // um worker por CPU
}
} else {
require("./servidor"); // cada worker corre o servidor
}El cluster crea un proceso por núcleo de CPU, compartiendo el mismo puerto. Sortea el límite single-thread de Node para usar todos los colores.
Crypto: encriptación AES
const crypto = require("crypto");
const clave = crypto.scryptSync("contrasena", "salt", 32);
const iv = crypto.randomBytes(16);
const cifra = crypto.createCipheriv("aes-256-cbc", clave, iv);
const encriptado = Buffer.concat([
cifra.update("mensaje secreta", "utf-8"),
cifra.final(),
]);createCipheriv encripta con AES (reversible). Deriva la clave con scryptSync y usa un iv aleatorio. Para desencriptar usa createDecipheriv.
Performance (perf_hooks)
const { performance } = require("perf_hooks");
const inicio = performance.now();
// ... operación ...
const duración = performance.now() - inicio;
console.log(`Demorou ${duración.toFixed(2)}ms`);
// Medir con marcador
performance.mark("inicio");
performance.mark("fin");
performance.measure("op", "inicio", "fin");perf_hooks mide el tiempo con alta precisión (milisegundos decimales). performance.now() es más preciso que Date.now().
Worker Threads
const { Worker, isMainThread, parentPort } =
require("worker_threads");
if (isMainThread) {
const w = new Worker(__filename);
w.postMessage("calcular");
w.on("message", (r) => console.log(r));
} else {
parentPort.on("message", () => {
parentPort.postMessage("hecho: " + 40 * 40);
});
}Los worker_threads corren JavaScript en paralelo (threads reales), ideal para tareas CPU-intensive. Se comunican por postMessage.
WebSockets (ws)
npm install ws
const { WebSocketServer } = require("ws");
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
ws.on("message", (msg) => {
ws.send("eco: " + msg);
});
});Los WebSockets mantienen una conexión bidireccional en tiempo real. El paquete ws crea el servidor; cada connection recibe y envía mensajes.