El Patrón Backend for Frontend: APIs que tu UI realmente quiere

El Patrón Backend for Frontend: APIs que tu UI realmente quiere

Todo desarrollador frontend lo ha vivido. La API devuelve un payload técnicamente correcto pero completamente inadecuado para lo que estás construyendo. Necesitas el nombre de un usuario, su avatar y sus tres pedidos más recientes en la página de inicio. En cambio, haces cuatro llamadas API separadas, ensamblas los datos en el cliente y rezas para que la red sea lo suficientemente rápida como para que los usuarios no noten la secuencia de carga.

O peor: el endpoint devuelve 47 campos cuando necesitas 6, y el único que realmente necesitas se llama algo como usr_acct_disp_nm.

Esto no es un problema de calidad del backend. Es un problema de arquitectura. El backend fue diseñado para uso general — o para un cliente completamente diferente. El patrón Backend for Frontend (BFF) existe para solucionar esto.

¿Qué es un BFF?

El término fue acuñado en SoundCloud y formalizado por Sam Newman en su trabajo sobre microservicios. La idea central es simple: en lugar de construir una API genérica que intenta servir a todas las superficies cliente (web, móvil, TV, integraciones de terceros), construyes una capa de servidor dedicada para cada cliente.

Un BFF es un servidor que:

El principio de "un BFF por superficie cliente" no es negociable. Una aplicación móvil y una aplicación web tienen necesidades de datos diferentes, presupuestos de rendimiento diferentes y patrones de interacción diferentes. En cuanto compartes un BFF entre ellas, vuelves a construir una API genérica que no optimiza para nadie.

BFF vs. Backend convencional

Un backend convencional se ocupa de la lógica de negocio y la integridad de los datos. Aplica las reglas del dominio, posee la base de datos y expone los recursos en términos que el dominio entiende. No sabe — ni debería saber — que tu página de inicio necesita una forma ligeramente diferente de User que tu página de perfil.

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

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

Arquitectura: Sin BFF vs. 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 dashboard 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 lógica de agregación y transformación vive en el BFF — no dispersa por el frontend, no duplicada en múltiples pantallas.

BFF en un contexto de Microservicios vs. Monolito

Microservicios: el caso de uso clásico

El patrón BFF nació del mundo de los microservicios, y con razón. Cuando tu backend está dividido entre una docena de servicios — usuarios, pedidos, inventario, notificaciones, facturación — el frontend tiene que conocerlos todos. Un pequeño cambio de UI puede requerir coordinar con tres equipos backend para obtener una forma de respuesta ligeramente diferente.

Un BFF resuelve esto actuando como el contrato único del frontend. El BFF habla con los servicios downstream que necesita. Si el servicio de usuarios cambia su API, el BFF absorbe el cambio. El frontend no se entera.

Este es el patrón anti-corruption layer aplicado a la frontera frontend/backend: aislar al frontend de la evolución constante del backend.

Monolito: un enfoque diferente

Los BFFs son igualmente útiles con un monolito — la motivación simplemente cambia. No estás gestionando la orquestación de múltiples servicios; estás gestionando una API genérica que no fue diseñada para los requisitos específicos de tu UI.

En este contexto, un BFF suele ser una fina capa de adaptación frente a tu monolito. Puede vivir en el mismo codebase (como un módulo, un conjunto de route handlers de Next.js o una app Express dedicada en un monorepo), o como un servicio separado.

La regla crítica para el caso monolito: mantén el BFF delgado. Un BFF que empieza a replicar 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 más frecuentemente se equivocan con el patrón.

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

Si los ingenieros backend son los dueños del BFF, este deriva lentamente hacia una API genérica. Los ciclos de revisión se vuelven más lentos. El equipo frontend termina negociando formas de respuesta mediante pull requests y tickets de JIRA en lugar de simplemente entregarlas. Has intercambiado un problema por una versión más lenta del mismo problema.

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

Esto requiere que los ingenieros frontend escriban algo de código del lado del servidor. No es mucho — el código BFF es principalmente trabajo de pegamento: llamar a este servicio, transformar esa respuesta, devolver una forma limpia. Pero requiere cierta comodidad con HTTP, flujos de autenticación e I/O asíncrono. La mayoría de los ingenieros capaces de escribir aplicaciones React complejas pueden manejar esto sin dificultad.

El límite de responsabilidad es el BFF en sí. El BFF llama a servicios downstream pero nunca posee lógica de negocio. Las escrituras en base de datos, las reglas del dominio y los invariantes se quedan en el backend. El BFF está firmemente en la capa de presentación, y esa distinción debe mantenerse activamente.

Elección del lenguaje

Sí, importa — especialmente cuando los ingenieros frontend son los principales responsables.

TypeScript con un runtime Node.js es mi recomendación por defecto. El razonamiento es práctico más que dogmático:

Go es una alternativa razonable cuando tu organización ya tiene experiencia en Go y se espera que el BFF sea mantenido por ingenieros full-stack o backend. Go maneja bien la concurrencia, lo que importa cuando un BFF hace varias peticiones downstream simultáneamente para construir una sola respuesta.

Pero la elección del lenguaje debe seguir el modelo de responsabilidad. Si los ingenieros frontend son dueños del BFF, deben usar el lenguaje que ya conocen. Cualquier otra cosa crea fricción, y la fricción eventualmente empuja la responsabilidad de vuelta a los equipos backend.

Seguridad: lo que el BFF te da gratis

La seguridad es uno de los argumentos más sólidos a favor de un BFF, y consistentemente se subestima.

El problema del almacenamiento de tokens

Las aplicaciones de una sola página tienen un dilema persistente: ¿dónde almacenas el token de acceso? localStorage es conveniente pero está expuesto a cualquier JavaScript en la página — un riesgo real en la era de los ataques a la cadena de suministro de npm. El almacenamiento en memoria es más seguro pero se pierde al recargar. Las cookies HTTP-only funcionan pero requieren manejo del lado del servidor.

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

En su lugar, el BFF realiza el intercambio de tokens. Recibe el código de autorización de tu proveedor de identidad, lo intercambia por un token de acceso y refresco, almacena los tokens en el servidor y le da al navegador una cookie de sesión HTTP-only. Cada petición posterior del frontend lleva esa cookie; el BFF busca la sesión, adjunta el token real a la petición downstream y la reenvía. El token nunca toca 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 acumulan las reglas del dominio. Limítalo a lo que está diseñado para hacer: agregación de lecturas, dar forma a las respuestas y delegación de autenticación.

Específicamente:

Si tu BFF tiene su propia base de datos y está empezando a poseer reglas de negocio, has dejado de construir un BFF y has empezado a construir un segundo backend. Ese es un problema completamente diferente.

Despliegue

Co-localizado con el frontend

Si estás usando Next.js, ya tienes la infraestructura para un BFF ligero: 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 punto de partida correcto para aplicaciones pequeñas y medianas. El compromiso es que no puedes escalar el BFF independientemente del frontend — pero 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 TypeScript, pero se despliegan de forma independiente. El frontend llama al BFF via HTTP; el BFF se distribuye hacia los servicios backend.

Esta configuración es más compleja operacionalmente pero ofrece escalado independiente y una separación más clara. Tiene sentido cuando el BFF tiene suficiente complejidad — múltiples patrones de agregación, capas de caché, pool de conexiones — para justificar la sobrecarga operacional.

Sidecar en Kubernetes

En un entorno Kubernetes, el BFF puede desplegarse como un contenedor sidecar en el mismo pod que el servidor frontend. El frontend se comunica con el BFF via localhost, manteniendo latencia casi nula sin salto de red externo. Siempre se despliegan juntos, lo que elimina el desajuste de versiones entre los dos.

Qué evitar

No compartas un BFF entre múltiples aplicaciones frontend. En el momento en que lo haces, estás construyendo una API gateway, no un BFF. Has reintroducido el mismo problema de generalización que intentabas resolver, solo que una capa más arriba.

Ejemplos del mundo real

SoundCloud es donde el patrón fue formalmente nombrado. 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 diferentes, y una API genérica compartida significaba negociación constante entre equipos frontend y backend. Dividirse en BFFs específicos por cliente restauró la velocidad de entrega.

Netflix ejecuta una capa BFF para cada uno de los dispositivos que soporta — y hay muchos. Televisores, teléfonos, tabletas, consolas de videojuegos y navegadores web tienen presupuestos de rendimiento y restricciones de renderizado significativamente diferentes. Una sola API no puede servir bien a todos ellos. La capa BFF de Netflix gestiona la traducción para cada superficie.

Spotify usa un modelo similar. El reproductor web, el cliente de escritorio y las apps móviles tienen requisitos de datos y objetivos de rendimiento diferentes. Su capa BFF gestiona la distribución hacia los servicios internos y devuelve respuestas adaptadas a cada cliente.

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

Cuándo NO usar un BFF

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

Omite el BFF si:

El BFF más caro es el que construiste antes de necesitarlo. El scaffolding añade sobrecarga cognitiva, y sin un problema de coordinación real que absorber, es simplemente código extra que mantener.

Conclusión

El patrón BFF es, en esencia, una herramienta de organización de equipos disfrazada de patrón de arquitectura.

Dice: el equipo que construye la UI debe poseer el contrato API que la alimenta. Todo lo demás se deriva de eso. La elección del lenguaje (TypeScript) se deriva de la responsabilidad. El modelo de seguridad (tokens mantenidos en el servidor) se deriva de que el BFF es una capa backend de confianza. La estrategia de despliegue se deriva del hecho de que frontend y BFF evolucionan juntos.

Úsalo cuando tengas un problema de coordinación — cuando los ingenieros frontend están esperando a que los equipos backend remodelen respuestas, o cuando estás sirviendo a múltiples superficies cliente con necesidades significativamente diferentes. No lo uses para resolver un problema que aún no tienes.

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

© Melvin Laplanche - All rights reserved.