Explica cómo transmitir responses grandes con streams, respetar backpressure, manejar errores y aborts y evitar cargar archivos completos en memoria.
Última actualización
Actualizada
Nivel
Profundización
Un stream permite procesar datos por partes sin esperar a tenerlos completos en memoria. La idea central no es “hacer todo más rápido”, sino mantener el consumo de memoria y la presión entre productor y consumidor bajo control.
request es un Readable: el cliente produce bytes y la aplicación los consume.
response es un Writable: la aplicación produce bytes y el cliente los consume.
Express añade helpers, pero no elimina este comportamiento. Cuando utilizas express.json(), el middleware consume el stream completo, aplica límites, interpreta los bytes y finalmente asigna un valor a request.body. Cuando envías un archivo o un CSV grande, puedes evitar construir todo el contenido en memoria conectando una fuente directamente con la response.
Sin streaming, una exportación puede seguir este flujo:
Texto
consultar 200 000 filas
↓
crear un array enorme
↓
convertir todo a CSV
↓
guardar un string o Buffer completo
↓
enviar la response
El coste crece con el volumen. La aplicación retiene memoria hasta completar la operación, aumenta la pausa del garbage collector y no empieza a entregar datos hasta terminar todo el cálculo.
Cada etapa posee una capacidad interna. Cuando el destino no puede consumir al mismo ritmo, comunica backpressure para que la fuente reduzca o pause la producción.
Backpressure no significa que la red nunca acumule datos. Significa que el pipeline dispone de señales para evitar que el productor genere indefinidamente más de lo que el consumidor puede manejar.
response.write() devuelve false cuando el buffer interno alcanzó su umbral. Ignorar ese valor y continuar escribiendo puede aumentar memoria, especialmente con clientes lentos.
Normalmente no necesitas manejar esto manualmente. pipeline() conecta streams y coordina propagación de presión, errores y cierre.
El ejemplo evita cargar todas las órdenes simultáneamente. El repository debe producir filas de forma incremental —por cursor, stream del driver o lotes— y cerrar correctamente su recurso al terminar o abortar.
Un stream binario mueve Buffer o strings. Un stream con objectMode: true puede mover objetos JavaScript entre etapas.
Object mode facilita transformaciones como fila → CSV, pero cada objeto conserva su coste en memoria. No convierte una consulta que carga todo el array en una consulta incremental. La fuente debe ser streaming desde el comienzo.
try{awaitpipeline(source, response);}catch(error){if(response.headersSent){
response.destroy(error as Error);return;}throw error;}
Antes de enviar headers, el error middleware todavía puede construir una respuesta consistente. Después, lo más seguro suele ser cerrar la conexión y registrar el fallo.
El writable aplica backpressure, pero mantiene la conexión y algunos recursos abiertos. Define timeouts y límites de concurrencia para descargas grandes.
El cliente recibirá un archivo incompleto. No existe una respuesta HTTP posterior que repare el contenido. Puedes incluir checksums, jobs de exportación o archivos generados previamente para operaciones críticas.
Usar streams no evita bloquear el event loop. Compresión intensa, cifrado o transformación pesada puede requerir workers, límites o procesamiento fuera del request.
Videos y archivos grandes pueden requerir Range, 206 Partial Content, Content-Range y seek en la fuente. pipeline por sí solo no implementa ese contrato.