DevTools

Cheatsheet Node.js

Runtime JavaScript server-side

Volver a los lenguajes
Node.js
101 tarjetas encontradas
Categorías:
Versiones:

Módulos e Setup


11 cards
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)); // 5

CommonJS 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); // 8

Node 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.js

Las 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


11 cards
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 existe

readdir 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ón

stat 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


12 cards
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 timeout

setTimeout 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/5

process.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 → Timers

El Event Loop procesa: código síncrono → microtasks (Promises) → timers (setTimeout). Incluso con delay 0, el timer corre en último lugar.

HTTP e Servidor


10 cards
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


12 cards
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 cuerpo

El 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.css

express.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


12 cards
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


11 cards
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/CD

El 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 test

Los 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 workspace

Los 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.0

Para 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 disponible

El 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


11 cards
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
  .gitignore

Separa 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.js

NODE_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 vitest

Vitest (o Jest) prueba funciones de forma aislada. describe agrupa tests y expect verifica resultados. Se ejecuta con npx vitest.

Avançado


11 cards
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 temporal

El 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.