La idea detrás del nombre
Un pipeline es una secuencia de etapas conectadas donde cada etapa recibe la salida de la anterior, la procesa y entrega su resultado a la siguiente. La materia que viaja por el pipeline puede ser desde un número hasta una petición HTTP completa.
El término viene de las líneas de ensamblaje, pero de Software la analogía más útil es la de una tubería: los datos entran por un extremo, avanzan por dentro y salen por el otro. Cada tramo puede transformar lo que pasa, filtrarlo o decidir si continúa.
En backend la versión más típica es una petición HTTP que atraviesa etapas hasta volverse una respuesta:
Request
-> Autenticación
-> Validación
-> Lógica de negocio
-> Base de datos
-> Response
1. La versión más simple: encadenar funciones
En Node.js la forma más básica de un pipeline es componer funciones, donde la salida de una es la entrada de la siguiente.
function quitarEspacios(texto) {
return texto.trim();
}
function aMayusculas(texto) {
return texto.toUpperCase();
}
function anadirPrefijo(texto) {
return 'PIPE: ' + texto;
}
const resultado = anadirPrefijo(aMayusculas(quitarEspacios(' hola mundo ')));
Cada función recibe exactamente lo que la anterior devolvió:
- entra
hola mundo-> salehola mundo - entra
hola mundo-> saleHOLA MUNDO - entra
HOLA MUNDO-> salePIPE: HOLA MUNDO
Ya existe un pipeline: hay etapas, hay un orden y hay un dato que las atraviesa. Lo que falta es un mecanismo para organizarlo sin anidar llamadas.
function pipeline(dato, etapas) {
let resultado = dato;
for (const etapa of etapas) {
resultado = etapa(resultado);
}
return resultado;
}
const texto = pipeline(' hola mundo ', [
quitarEspacios,
aMayusculas,
anadirPrefijo
]);
Las entradas y salidas de cada etapa son las mismas, pero ahora el orden se lee de arriba hacia abajo y agregar una etapa nueva es sumar una función a la lista.
2. La versión asíncrona: Promises
En backend las etapas casi nunca son síncronas. Validar contra una base de datos, consultar un servicio o escribir en un log toma tiempo. Cuando cada etapa devuelve una Promise, el encadenado se vuelve natural con async/await.
async function autenticar(req) {
const usuario = await buscarPorToken(req.headers.authorization);
if (!usuario) throw new Error('No autorizado');
return usuario;
}
async function validar(datos) {
if (!datos.nombre) throw new Error('Falta el nombre');
return datos;
}
async function guardar(usuario, datos) {
return crearEnBaseDeDatos({ autor: usuario, ...datos });
}
Cada etapa declara qué espera y qué produce, por eso puede probarse sola. El pipeline asíncrono solo las junta:
async function ejecutar(req) {
const usuario = await autenticar(req);
const datos = await validar(req.body);
const creado = await guardar(usuario, datos);
return creado;
}
La entrada es la petición y la salida es el registro creado. Una Promise además propaga errores: si una etapa lanza una excepción, el flujo se corta y puede llegar a un manejador central en lugar de estrellarse en silencio.
Cuando varias etapas necesitan datos previos, en la práctica se pasa un objeto de contexto que viaja por todo el pipeline, en lugar de encadenar un solo valor.
3. El pipeline real en Express: middleware
Express formaliza exactamente esta idea con middleware. Un middleware es una función que recibe la petición (req), la respuesta (res) y una función next que entrega el control a la siguiente etapa.
const express = require('express');
const app = express();
app.use((req, res, next) => {
req.inicio = Date.now();
next();
});
app.use((req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'No autorizado' });
}
req.usuario = { id: 1, nombre: 'Arturo' };
next();
});
app.post('/usuarios', (req, res) => {
res.json({ ok: true, usuario: req.usuario });
});
Cada etapa agrega información o valida, y el objeto req actúa como el contexto que viaja entre etapas:
- registrar timestamp -> entra la petición, sale la misma petición con req.inicio
- autenticar -> entra la petición, sale la petición con req.usuario
- manejador final -> recibe los datos ya preparados y construye la response
Si una etapa no llama a next, el flujo se detiene ahí. Esa capacidad es lo que permite cortar una petición con un 401 o un 400 antes de llegar a la lógica de negocio.
4. Cuándo algo es un pipeline y cuándo no
Un pipeline existe cuando se cumplen estas condiciones:
- hay varias etapas con una responsabilidad única;
- cada etapa recibe el resultado de la anterior;
- existe un orden, y dentro de ese orden las etapas transforman o deciden;
- el conjunto puede describirse como entrada -> etapas -> salida.
No es un pipeline cuando:
- todo el procesamiento vive dentro de un solo handler gigante que mezcla validación, base de datos y respuesta;
- las funciones se ejecutan en paralelo sin que una alimente a la otra;
- cada pieza decide por sí sola el flujo completo y no existe un contrato sobre lo que entra y sale.
La diferencia no está en la cantidad de archivos, sino en que el dato fluya de etapa en etapa siguiendo un contrato.
5. Relación con streams y procesamiento de datos
Node.js tiene además un concepto hermano: los Streams. Un stream también es un pipeline, pero con una diferencia importante: no espera a tener todos los datos para empezar a procesarlos.
const fs = require('fs');
const { Transform } = require('stream');
const aMayusculas = new Transform({
transform(fragmento, encoding, callback) {
callback(null, String(fragmento).toUpperCase());
}
});
fs.createReadStream('entrada.txt')
.pipe(aMayusculas)
.pipe(fs.createWriteStream('salida.txt'));
Aquí pipe es literal: conecta la salida de un stream con la entrada del siguiente. Los datos se procesan por fragmentos conforme llegan, no en bloque, lo que permite manejar archivos enormes sin cargarlos todos en memoria.
Con esto el modelo mental cubre tres formas de pipeline:
Funciones -> etapa síncrona, entrada completa y salida sincrónica
Promises -> etapa asíncrona, cada paso resuelve antes de seguir
Middleware -> pipeline de Express, contexto compartido entre etapas
Streams -> pipeline de datos en movimiento, procesa por fragmentos
La misma metáfora aparece fuera del código: un pipeline de CI/CD hace pasar cada commit por etapas como build, test y deploy, y cada etapa decide si el flujo continúa o se detiene.
El modelo mental al revisar código
Cuando veas código backend, pregúntate cinco cosas:
- ¿Cuál es la entrada? (petición, mensaje, evento, archivo)
- ¿Qué etapas la atraviesan? (middleware, servicios, validaciones)
- ¿Qué transformación hace cada etapa?
- ¿Qué es la salida? (response, registro en base de datos, archivo)
- ¿Qué contrato conecta una etapa con la siguiente?
Si puedes responderlas, estás viendo un pipeline aunque el código nunca use la palabra pipeline.
Consideraciones
Un pipeline no es una biblioteca ni un patrón rigidizado: es un modelo mental para ordenar el procesamiento por etapas. En Node.js aparece en tres lugares naturales: las cadenas de middleware de Express, las cadenas de Promises y los streams conectados con pipe. Reconocer esas tres variantes hace que una base de código backend sea mucho más fácil de leer y de mantener.
© Arturo Juárez Monroy