APIs avanzadas y adopción progresiva en Node.js | Nicolás Garzón
Node.js incorpora APIs web, diagnóstico, almacenamiento, permisos y distribución que evolucionan a ritmos distintos. Antes de adoptar una capacidad reciente, comprueba cuatro cosas: estabilidad en la versión desplegada, requisitos mínimos, límites operativos y beneficio real . Una API presente no es automáticamente una dependencia adecuada para producción.
Texto
Copiar problema concreto
→ API disponible en la versión objetivo
→ comprobar estabilidad y limitaciones
→ encapsular detrás de una frontera
→ tests + métricas + fallback
→ rollout progresivoLa documentación de la versión instalada es la fuente de verdad. Una API puede ser estable en Node 26 y todavía experimental o release candidate en Node 24.
Estado Interpretación Stable Compatibilidad prioritaria; puede formar parte del contrato normal. Release candidate No se esperan grandes cambios, pero aún puede cambiar antes de estabilizarse. Active development API experimental cercana a viabilidad mínima; evita depender de ella sin aislamiento. Experimental No está protegida plenamente por semver y puede cambiar o desaparecer. Legacy Sigue soportada, pero existen alternativas preferidas y recibe menos evolución.
No uses únicamente el número global del módulo: algunas funciones dentro de un módulo pueden tener estabilidad diferente.
Node moderno expone APIs compatibles con la plataforma web:
fetch.
Request y Response.
Headers.
FormData.
Blob y File.
URL y URLSearchParams.
AbortController y AbortSignal.
Web Streams.
WebSocket.
Web Crypto mediante globalThis.crypto.
Aportan portabilidad entre navegador, runtimes serverless y Node, pero el entorno sigue siendo distinto:
Texto
Copiar same interface
≠ same network stack
≠ same filesystem
≠ same lifecycle
≠ same security contextJavaScript
Copiar const signal = AbortSignal. timeout ( 5_000 ) ;
const response = await fetch ( "https://api.example.com/orders" , { signal } ) ;
if ( ! response. ok) {
throw new Error ( ` Upstream returned ${ response. status} ` ) ;
}
No rechaza automáticamente por 404 o 500.
El body es un stream y solo puede consumirse una vez salvo clonación.
Pooling, DNS y proxies dependen de la implementación y configuración.
Una URL externa puede introducir SSRF.
Debes limitar tiempo, redirects y tamaño de respuesta.
JavaScript
Copiar AbortSignal. timeout ( 5_000 ) ;
AbortSignal. any ( [ requestSignal, shutdownSignal] ) ; Un timeout local no basta si las capas internas ignoran la señal. Propágala hasta DB, fetch, timers, pipeline o trabajo propio cuando la API lo permita.
La implementación global compatible con navegador es estable en líneas modernas:
JavaScript
Copiar const socket = new WebSocket ( "wss://example.com/events" ) ; No incluye automáticamente:
Reconexión.
Heartbeat.
Autenticación renovable.
Backpressure de negocio.
Persistencia de mensajes.
Escalado entre réplicas.
Para un servidor WebSocket todavía puedes necesitar una librería o infraestructura específica; la API global documentada representa principalmente el cliente.
ReadableStream, WritableStream y TransformStream son estables en líneas LTS modernas. Node Streams continúan siendo fundamentales para fs, http y ecosistema.
JavaScript
Copiar import { Readable } from "node:stream" ;
const webStream = Readable. toWeb ( nodeReadable) ;
const nodeStream = Readable. fromWeb ( webReadable) ;
Backpressure.
Cancelación.
Errores.
Object mode.
Conversión de chunks.
No conviertas repetidamente entre modelos dentro de una ruta caliente sin medir.
globalThis.crypto.subtle facilita código portable. node:crypto ofrece además streams, utilidades de certificados, password derivation y capacidades específicas.
Algoritmo.
Portabilidad.
Formato de claves.
Streaming.
Versión mínima.
Ninguna de las dos APIs justifica inventar protocolos criptográficos.
El Permission Model es estable en las líneas soportadas modernas y se habilita con:
Bash
Copiar node --permission \
--allow-fs-read= ./config \
--allow-fs-write= ./tmp \
app.jsRestringe capacidades como:
Lectura y escritura de filesystem.
Red, según versión y flags disponibles.
Child processes.
Worker threads.
Native addons.
WASI.
Inspector.
JavaScript
Copiar if ( ! process. permission. has ( "fs.read" , configPath) ) {
throw new Error ( "Missing config permission" ) ;
} Funciona como un seat belt para código confiable: evita que una aplicación o dependencia acceda accidentalmente a recursos no concedidos.
No es un sandbox contra código malicioso. La política oficial de Node asume que el código ejecutado es confiable y documenta formas y limitaciones que pueden eludir la expectativa de aislamiento.
Usuario del sistema sin privilegios.
Container/seccomp cuando corresponda.
Filesystem y red restringidos.
Secrets mínimos.
Dependencias revisadas.
Permisos no revocan recursos ya abiertos.
File descriptors existentes pueden escapar a controles posteriores.
Symlinks requieren cuidado.
Flags que leen archivos antes de inicializar el modelo tienen comportamiento especial.
Addons o APIs nativas pueden ampliar superficie.
Workers y child processes necesitan política explícita y comportamiento dependiente de versión.
No promociones el Permission Model como defensa absoluta ante paquetes maliciosos.
node:sqlite permite trabajar con SQLite sin dependencia externa:
JavaScript
Copiar import { DatabaseSync } from "node:sqlite" ;
const database = new DatabaseSync ( ":memory:" ) ;
database. exec ( `
CREATE TABLE notes (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
) STRICT
` ) ;
const insert = database. prepare (
"INSERT INTO notes (title) VALUES (?)"
) ;
insert. run ( "Event loop" ) ; En la línea Node 24 actual se encuentra como release candidate , no como API completamente estable. Verifica el estado exacto antes de convertirla en contrato de una librería.
CLIs.
Herramientas locales.
Tests e integración.
Aplicaciones single-instance.
Cache o catálogo local.
Prototipos con persistencia real.
DatabaseSync bloquea el hilo durante las operaciones.
SQLite sigue teniendo modelo de locking y concurrencia propio.
No reemplaza PostgreSQL para cualquier carga multiusuario.
Debes usar prepared statements y transacciones.
Backups, WAL, migrations y permisos siguen siendo responsabilidades reales.
Para requests concurrentes, mide latencia y considera worker dedicado, cola o una base cliente-servidor cuando la carga lo justifique.
La compile cache persiste bytecode/código de V8 para reducir compilación en arranques posteriores:
JavaScript
Copiar import { enableCompileCache } from "node:module" ;
enableCompileCache ( ) ; También puede activarse mediante:
Bash
Copiar NODE_COMPILE_CACHE = /tmp/node-compile-cache node app.js
Puede acelerar arranques repetidos.
El primer arranque puede no mejorar.
La cache normalmente depende de la versión de Node.
Puede incluir módulos JavaScript y TypeScript.
El modo portable depende de versiones recientes.
La cobertura V8 puede ser menos precisa si se reutiliza bytecode cacheado.
Por eso, desactívala durante coverage cuando necesites precisión:
Bash
Copiar NODE_DISABLE_COMPILE_CACHE = 1 node --test --experimental-test-coverageNo trates una cache de compilación como artefacto durable entre versiones o arquitecturas sin probarlo.
node:module permite hooks de resolución y carga. Pueden servir para:
Instrumentación.
Transformaciones controladas.
Protocolos internos.
Resolución personalizada.
Pero alteran una de las partes más sensibles del runtime:
Texto
Copiar specifier
→ resolve hook
→ load hook
→ module evaluation
Diferencias entre hooks async y sync.
Overhead en cada import.
Caching inesperado.
Debugging complejo.
Incompatibilidad entre versiones.
Superficie supply-chain.
Prefiere exports, imports y loaders estándar antes de crear resolución personalizada.
SEA permite distribuir una aplicación Node dentro de un ejecutable. Es útil para CLIs o entornos donde no quieres instalar Node por separado.
Estado de estabilidad en tu versión.
Assets y configuración.
Native addons.
Firma del binario.
Actualización y rollback.
Tamaño.
Compatibilidad por sistema operativo y arquitectura.
No es “compilar JavaScript a máquina” en el sentido tradicional: empaqueta runtime y aplicación bajo un proceso de construcción específico.
Snapshots V8 pueden precargar estado para mejorar startup. Son avanzados porque el estado capturado debe ser seguro y reproducible.
Secrets.
Sockets.
File descriptors.
Estado dependiente de entorno.
Datos que deban refrescarse.
Mide cold start real antes de introducir un pipeline de snapshots.
node:vm ejecuta código en contextos diferentes, pero no constituye una frontera de seguridad para código hostil.
Texto
Copiar separate context
≠ separate process
≠ resource isolation
≠ secure sandboxPara plugins no confiables utiliza aislamiento diseñado para ello: proceso separado, container, límites de CPU/memoria y protocolo mínimo.
Node-API permite integrar C/C++ con estabilidad ABI mejor que usar internals de V8 directamente.
Bloquear el event loop.
Crear threads.
Corromper memoria.
Saltarse supuestos del Permission Model.
Complicar instalación multi-plataforma.
Úsalo solo cuando el rendimiento o acceso nativo justifique el coste. Prefiere prebuilds firmados, Node-API y fallback cuando sea posible.
Wasm aporta un formato portable para código compilado. No garantiza:
Mejor rendimiento.
Seguridad automática.
Menor memoria.
I/O eficiente.
Mide el coste de copiar datos entre JavaScript y Wasm. Para trabajo CPU-bound grande, un worker puede seguir siendo necesario para no bloquear el hilo principal.
Debugging remoto.
CPU profiles.
Heap snapshots.
Evaluación de código.
Nunca lo expongas a internet sin una frontera fuerte. Quien accede al inspector puede controlar el proceso y leer memoria/secrets.
Bash
Copiar node --inspect = 127.0 .0.1:9229 app.jsPrefiere loopback, túnel autenticado y ventanas temporales de diagnóstico.
Una API nueva no debe entrar solo porque elimina una dependencia. Evalúa:
¿Qué versión mínima exige?
¿Es estable o experimental?
¿Funciona en todos los entornos de despliegue?
¿Qué comportamiento pierdo frente a la librería actual?
¿Cómo pruebo y observo el cambio?
¿Existe rollback?
¿Afecta librerías consumidoras?
Capacidad Uso recomendado Precaución principal Fetch/WebSocket Clientes web portables Timeout, SSRF, pooling, reconexión Web Streams Interop con APIs web Conversión y backpressure Permission Model Defensa adicional No es sandbox `node:sqlite` Persistencia local RC y operaciones síncronas Compile cache Startup repetido Versionado y coverage SEA Distribuir CLIs Build y plataforma VM Contextos controlados No ejecutar código hostil Wasm/addons Trabajo especializado Memoria, bloqueo y supply chain
Para cada capacidad reciente prueba:
Versión mínima y máxima soportada.
Linux/macOS/Windows si distribuyes CLI.
Cancelación y shutdown.
Fallo de permisos.
Cold y warm startup.
Memory/CPU overhead.
Compatibilidad con coverage.
Rollback a implementación anterior.
Una LTS anterior puede no tener la API o tener otro estado.
No protege frente a código malicioso.
Puede bloquear requests concurrentes.
El aislamiento es insuficiente.
Entrega control del proceso.
La API nativa puede no incluir retry, pooling, server mode o ergonomía equivalente.
La estabilidad se verifica por versión y API concreta.
APIs web mejoran portabilidad, no igualan runtimes.
Permission Model es una defensa adicional para código confiable.
node:sqlite sigue siendo release candidate en la línea LTS revisada.
Compile cache optimiza startup y necesita política de versión/coverage.
SEA, hooks, snapshots, Wasm y addons deben resolver una necesidad medida.
VM no es sandbox.
Inspector es una capacidad altamente sensible.
Toda adopción necesita frontera, tests, métricas y rollback.
¿Por qué una API estable en Node 26 puede no ser adecuada para Node 24?
¿Qué amenaza no resuelve el Permission Model?
¿Qué riesgo tiene DatabaseSync en un servidor?
¿Por qué desactivarías compile cache durante coverage?
¿Qué diferencia hay entre un contexto VM y un proceso aislado?
¿Qué debes probar al convertir Node Streams a Web Streams?
Ver respuestas
Porque puede no existir o tener otro estado y contrato en la LTS desplegada.
Código deliberadamente malicioso; no es un sandbox.
Sus operaciones síncronas pueden bloquear el hilo principal.
La cache de V8 puede reducir la precisión de cobertura.
VM comparte proceso y recursos; un proceso aporta memoria y lifecycle separados.
Backpressure, errores, chunks y cancelación.
TypeScript nativo en Node.js diferencia ejecución directa, type stripping, type checking y compilación.