El patrón Backend for Frontend: APIs que tu UI de verdad quiere

El patrón Backend for Frontend: APIs que tu UI de verdad quiere

Todo desarrollador de frontend ha visto una API devolver una carga que es técnicamente correcta, pero que no sirve para lo que está construyendo. Necesitas el nombre de un usuario, su avatar y sus tres pedidos más recientes en la página de inicio. Haces cuatro llamadas por separado, unes los datos en el cliente y esperas que la red sea lo bastante rápida para que nadie note la secuencia de carga. Un endpoint devuelve 47 campos cuando necesitas 6, y el único campo que necesitas se llama algo como usr_acct_disp_nm.

Esto es un problema de arquitectura. El backend se diseñó para un uso general o para otro cliente por completo. El patrón Backend for Frontend (BFF) existe para arreglar esto.

¿Qué es un BFF?

El término nació en SoundCloud y lo formalizó Sam Newman en su trabajo sobre microservicios. La idea central es simple. Construyes una capa dedicada del lado del servidor para cada cliente. Una API genérica que intenta servir a todas las superficies (web, móvil, TV, integraciones de terceros) no le sirve bien a ninguna.

Un BFF es un servidor que:

El principio de un BFF por superficie de cliente no se negocia. Una app móvil y una app web tienen necesidades de datos, presupuestos de rendimiento y patrones de interacción distintos. Compartir un BFF entre las dos te regresa a construir una API de propósito general que no optimiza para nadie.

BFF frente a un backend normal

Un backend normal se ocupa de la lógica de negocio y la integridad de los datos. Aplica reglas de dominio, es dueño de la base de datos y expone recursos en los términos que entiende el dominio. No le importa que tu página de inicio necesite una forma de User ligeramente distinta a la de tu página de perfil.

Un BFF se ocupa de la lógica de presentación. Toma lo que ofrece el backend posterior y lo transforma en lo que necesita un frontend específico. En la práctica, eso significa:

La distinción es importante. Un ingeniero de backend piensa en entidades e invariantes. El ingeniero de frontend piensa en componentes y en los datos que necesita una pantalla. Un BFF habla con fluidez ese segundo lenguaje.

Arquitectura: sin BFF frente a con BFF

Architecture diagram: without BFF vs. with BFF

Así se ve la diferencia en la práctica. Sin un BFF, una página de panel podría hacer esto:

// El frontend ensambla la vista desde tres llamadas separadas
const [user, orders, notifications] = await Promise.all([
  fetch("/api/users/me"),
  fetch("/api/orders?userId=me&limit=3"),
  fetch("/api/notifications?userId=me&unread=true"),
]);
const unreadCount = notifications.filter((n) => !n.read).length;
const displayName = `${user.firstName} ${user.lastName}`;

Con un BFF, el frontend obtiene exactamente lo que necesita en una sola llamada:

// El BFF expone un endpoint diseñado para esta pantalla
const { user, recentOrders, unreadCount } = await fetch("/bff/dashboard");

La agregación y la transformación viven en el BFF. No están dispersas en el frontend ni duplicadas en varias pantallas.

BFF en un contexto de microservicios frente a monolito

Microservicios: el caso de uso clásico

El patrón BFF nació del mundo de los microservicios, y por una buena razón. Cuando tu backend está dividido en una docena de servicios (usuarios, pedidos, inventario, notificaciones, facturación), el frontend tiene que conocerlos todos. Un pequeño cambio de UI podría requerir coordinarse con tres equipos de backend para obtener una forma de respuesta un poco distinta.

Un BFF resuelve esto al actuar como el contrato único del frontend. El BFF habla con los servicios posteriores que necesite. Si el servicio de usuarios cambia su API, el BFF absorbe el cambio. Al frontend no le importa.

Este es el patrón anti-corrupción aplicado a la frontera entre frontend y backend. Aísla al frontend del trajín de la evolución del backend.

Monolito: una variante distinta

Los BFF son igual de útiles con un monolito. Solo cambia la motivación. No estás orquestando varios servicios. Estás lidiando con una API genérica que no se diseñó para tus requisitos específicos de UI.

En este contexto, un BFF suele ser una capa de adaptación delgada frente al monolito. Puede vivir en el mismo código (como un módulo, un conjunto de route handlers de Next.js o una app de Express dedicada dentro de un monorepo), o como un servicio aparte.

La regla crítica para el caso del monolito es mantener el BFF delgado. Un BFF que empieza a replicar la lógica de negocio del monolito es un anti-patrón. Debe traducir y agregar. Las reglas de negocio pertenecen al backend.

¿Quién escribe el BFF?

Aquí es donde los equipos se equivocan con más frecuencia.

El BFF debe ser propiedad del equipo de frontend. Ese es todo el punto del patrón.

Si los ingenieros de backend son dueños del BFF, este se va deslizando hacia una API de propósito general. Los ciclos de revisión se vuelven más lentos. El equipo de frontend termina negociando las formas de las respuestas a través de pull requests y tickets de JIRA. Las formas salen solo cuando el equipo de backend las aprueba. Has cambiado un problema por una versión más lenta del mismo problema.

Cuando los ingenieros de frontend son dueños del BFF, son dueños del contrato. Remodelan los datos cuando lo necesitan. Agregan un endpoint sin presentar una solicitud. Entregan a la velocidad del frontend.

Esto exige que los ingenieros de frontend escriban algo de código del lado del servidor. No es mucho. El código de un BFF es en su mayoría trabajo de pegamento. Llamas a este servicio, transformas esa respuesta, devuelves una forma limpia. Requiere comodidad con HTTP, flujos de autenticación y E/S asíncrona. La mayoría de los ingenieros que pueden escribir aplicaciones complejas de React lo manejan sin mucha dificultad.

La frontera de propiedad es el propio BFF. El BFF llama a los servicios posteriores, pero nunca es dueño de la lógica de negocio. Las escrituras en la base de datos, las reglas de dominio y los invariantes se quedan en el backend. El BFF está firmemente en la capa de presentación. Esa distinción hay que mantenerla de forma activa.

La elección del lenguaje

Sí, importa, sobre todo cuando los ingenieros de frontend son los propietarios principales.

TypeScript con un runtime de Node.js es mi recomendación por defecto. La razón es práctica.

Go es una alternativa razonable cuando tu organización de ingeniería ya tiene experiencia en Go y se espera que el BFF lo mantengan ingenieros full-stack o de backend. Go maneja bien el fan-out concurrente, y eso importa cuando un BFF hace varias solicitudes posteriores a la vez para construir una sola respuesta.

La elección del lenguaje debe seguir el modelo de propiedad. Si los ingenieros de frontend son dueños del BFF, deben usar el lenguaje que ya conocen. Cualquier otra cosa genera fricción, y la fricción termina empujando la propiedad de vuelta a los equipos de backend.

Seguridad: lo que el BFF te da gratis

La seguridad es uno de los argumentos más fuertes a favor de un BFF, y constantemente se subestima.

El problema del almacenamiento de tokens

Las aplicaciones de una sola página tienen un dilema conocido. ¿Dónde guardas el token de acceso? localStorage es conveniente, pero está expuesto a cualquier JavaScript de la página. Ese es un riesgo real en una era de ataques a la cadena de suministro de paquetes npm. La memoria es más segura, pero se pierde al refrescar. Las cookies HTTP-only funcionan, y exigen manejo del lado del servidor.

Un BFF resuelve este problema por completo. El navegador nunca tiene un token de acceso en bruto.

El BFF hace el intercambio de tokens. Recibe el código de autorización de tu proveedor de identidad, lo intercambia por un token de acceso y uno de refresco, guarda los tokens del lado del servidor y le entrega al navegador una cookie de sesión HTTP-only. Cada solicitud posterior del frontend lleva esa cookie. El BFF busca la sesión, le pone el token real a la solicitud posterior y la reenvía. El token nunca pasa por el terreno de JavaScript.

// El BFF gestiona el callback OAuth. El navegador nunca ve el token de acceso
app.get("/auth/callback", async (req, res) => {
  const { code } = req.query;
  const tokens = await identityProvider.exchangeCode(code);
 
  // Los tokens viven en el servidor
  await sessionStore.save(req.sessionId, tokens);
 
  res.setCookie("session", req.sessionId, {
    httpOnly: true,
    secure: true,
    sameSite: "strict",
  });
 
  res.redirect("/dashboard");
});

Qué no poner en un BFF

Un BFF nunca debe convertirse en un lugar donde se acumulen reglas de dominio. Mantenlo en lo que está diseñado para hacer. La agregación de lectura, la forma de las respuestas y la delegación de autenticación.

Específicamente:

Si tu BFF tiene su propia base de datos y empieza a ser dueño de reglas de negocio, dejaste de construir un BFF y empezaste a construir un segundo backend. Ese es un problema totalmente distinto.

Despliegue

Co-ubicado con el frontend

Si usas Next.js, ya tienes la infraestructura para un BFF ligero. Son los Route Handlers (App Router) o las API Routes (Pages Router). Tu frontend y tu BFF viven en el mismo proyecto y se despliegan juntos como una sola unidad.

Este es el valor por defecto correcto para aplicaciones pequeñas y medianas. La desventaja es que no puedes escalar el BFF de forma independiente del frontend. Para la mayoría de los equipos, esa restricción es irrelevante. Empieza aquí.

Servicio separado en un monorepo

Para aplicaciones más grandes, un patrón común es un servicio BFF dedicado que vive junto al frontend en un monorepo. Ambos comparten tipos de TypeScript, pero se despliegan de forma independiente. El frontend llama al BFF por HTTP. El BFF hace fan-out hacia los servicios de backend.

Esta configuración es más compleja a nivel operativo, pero te da escalado independiente y una separación más limpia. Tiene sentido una vez que el BFF tiene la complejidad suficiente para justificar la carga operativa. Eso significa varios patrones de agregación, capas de caché y pooling de conexiones.

Sidecar de Kubernetes

En un entorno de Kubernetes, el BFF puede desplegarse como contenedor sidecar en el mismo pod que el servidor del frontend. El frontend se comunica con el BFF a través de localhost, lo que mantiene la latencia casi en cero, sin salto de red externo. Siempre se despliegan juntos, lo que elimina el desfase de versiones entre los dos.

Qué evitar

No compartas un BFF entre varias aplicaciones de frontend. En el momento en que lo haces, estás construyendo una pasarela de API. Reintrodujiste el mismo problema de generalización que querías resolver, una capa más arriba.

Ejemplos del mundo real

SoundCloud es donde se nombró formalmente el patrón. Su equipo de ingeniería se enfrentó exactamente al problema de coordinación descrito arriba. Los clientes móvil y web necesitaban formas de datos distintas, y una API genérica compartida implicaba negociación constante entre los equipos de frontend y backend. Separarlos en BFF específicos por cliente restauró la velocidad de entrega.

Netflix ejecuta una capa de BFF para cada uno de los dispositivos que soporta. Son muchos. Las televisiones, los teléfonos, las tabletas, las consolas de juegos y los navegadores web tienen presupuestos de rendimiento y restricciones de renderizado muy distintos. Una sola API no puede servirlos bien a todos. La capa de BFF de Netflix maneja la traducción para cada superficie.

Spotify usa un modelo similar. El reproductor web, el cliente de escritorio y las apps móviles tienen cada uno requisitos de datos y objetivos de rendimiento distintos. Su capa de BFF maneja el fan-out hacia los servicios internos y devuelve respuestas adaptadas a cada cliente.

En los tres casos, el BFF es una decisión estructural que refleja cómo están organizados sus equipos de ingeniería y qué tan rápido necesitan entregar.

Cuándo NO usar un BFF

El patrón BFF añade un salto de red, un servicio que operar y un despliegue que gestionar. Esa carga tiene que ganarse su lugar.

Sáltate el BFF si:

El BFF más caro es el que construyes antes de tener una razón para hacerlo. El andamiaje añade carga cognitiva, y sin un problema real de coordinación que absorber, se convierte en código extra que mantener.

Conclusión

El patrón BFF es una herramienta de estructura de equipo disfrazada de patrón de arquitectura.

Dice que el equipo que construye la UI debe ser dueño del contrato de API que la alimenta. La elección del lenguaje (TypeScript) se deriva de la propiedad. El modelo de seguridad (tokens guardados del lado del servidor) se deriva de que el BFF es una capa de backend de confianza. El despliegue se deriva de que el frontend y el BFF evolucionan juntos.

Úsalo cuando tengas un problema de coordinación. Es decir, cuando los ingenieros de frontend esperan a que los equipos de backend remodelen las respuestas, o cuando sirves varias superficies de cliente con necesidades muy distintas. No lo uses para resolver un problema que aún no tienes.

Cuando lo necesitas, no hay nada mejor para darle a los equipos de frontend control total sobre el contrato entre su código y los datos que consume.

© Melvin Laplanche - All rights reserved.