
Tout développeur frontend a déjà reçu une réponse d'API techniquement correcte, mais totalement inadaptée à ce qu'il construit. Vous avez besoin du nom d'un utilisateur, de son avatar et de ses trois dernières commandes pour la page d'accueil. Vous lancez quatre appels séparés, vous recousez les données côté client, et vous espérez que le réseau est assez rapide pour que personne ne remarque la séquence de chargement. Un endpoint renvoie 47 champs alors qu'il vous en faut 6, et le seul qui vous intéresse s'appelle quelque chose comme usr_acct_disp_nm.
C'est un problème d'architecture. Le backend a été conçu pour un usage général, ou pour un autre client. Le pattern Backend for Frontend (BFF) existe précisément pour y répondre.
Le terme est né chez SoundCloud et a été formalisé par Sam Newman dans ses travaux sur les microservices. L'idée est simple. On construit une couche serveur dédiée pour chaque client. Une API générique qui prétend servir toutes les surfaces (web, mobile, TV, intégrations tierces) n'en sert bien aucune.
Un BFF est un serveur qui :
Le principe d'un BFF par surface client ne se négocie pas. Une application mobile et une application web n'ont pas les mêmes besoins de données, ni le même budget de performance, ni les mêmes schémas d'interaction. Partager un BFF entre les deux, c'est retomber dans une API généraliste qui n'optimise pour personne.
Un backend classique s'occupe de la logique métier et de l'intégrité des données. Il applique les règles du domaine, possède la base de données et expose des ressources telles que le domaine les comprend. Peu lui importe que votre page d'accueil ait besoin d'une forme de User légèrement différente de celle de votre page de profil.
Un BFF s'occupe de la logique de présentation. Il prend ce que le backend propose et le transforme en ce dont un frontend précis a besoin. Concrètement :
La distinction est essentielle. Un ingénieur backend raisonne en entités et en invariants. Un ingénieur frontend raisonne en composants et en données nécessaires à un écran. Un BFF parle cette deuxième langue couramment.
Voici la différence en pratique. Sans BFF, une page de tableau de bord peut faire ceci :
// Le frontend assemble la vue depuis trois appels séparés
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}`;Avec un BFF, le frontend obtient exactement ce qu'il lui faut en un seul appel :
// Le BFF expose un endpoint taillé pour cet écran
const { user, recentOrders, unreadCount } = await fetch("/bff/dashboard");L'agrégation et la transformation vivent dans le BFF. Elles ne sont ni éparpillées dans le frontend, ni dupliquées d'un écran à l'autre.
Le pattern BFF est né dans le monde des microservices, et pour une bonne raison. Quand votre backend est découpé en une dizaine de services (utilisateurs, commandes, inventaire, notifications, facturation), le frontend doit tous les connaître. Une petite modification d'interface peut exiger de coordonner trois équipes backend pour obtenir une forme de réponse légèrement différente.
Un BFF règle le problème en devenant le contrat unique du frontend. Il parle aux services en aval dont il a besoin. Si le service utilisateur change son API, le BFF absorbe le changement. Le frontend n'en a rien à faire.
C'est le pattern anti-corruption appliqué à la frontière frontend/backend. Il isole le frontend de l'évolution constante du backend.
Les BFF sont tout aussi utiles avec un monolithe. La motivation change simplement. Vous n'orchestrez pas plusieurs services. Vous avez affaire à une API générique qui n'a pas été conçue pour vos besoins d'interface précis.
Dans ce contexte, un BFF est en général une fine couche d'adaptation devant le monolithe. Elle peut vivre dans le même dépôt (comme un module, un ensemble de route handlers Next.js, ou une application Express dédiée dans un monorepo), ou dans un service séparé.
La règle d'or dans le cas du monolithe est de garder le BFF léger. Un BFF qui se met à reproduire la logique métier du monolithe est un anti-pattern. Il doit traduire et agréger. Les règles métier appartiennent au backend.
C'est ici que les équipes se trompent le plus souvent.
Le BFF doit appartenir à l'équipe frontend. C'est tout l'intérêt du pattern.
Si les ingénieurs backend possèdent le BFF, il dérive lentement vers une API généraliste. Les cycles de revue s'allongent. L'équipe frontend finit par négocier la forme des réponses à travers des pull requests et des tickets JIRA. Les formes ne partent que quand l'équipe backend les approuve. Vous avez échangé un problème contre une version plus lente du même problème.
Quand les ingénieurs frontend possèdent le BFF, ils possèdent le contrat. Ils remodelent les données quand il le faut. Ils ajoutent un endpoint sans déposer de demande. Ils livrent au rythme du frontend.
Cela exige que les ingénieurs frontend écrivent un peu de code serveur. Ce n'est pas grand-chose. Le code d'un BFF est surtout de la colle. Appeler ce service, transformer cette réponse, renvoyer une forme propre. Il faut être à l'aise avec HTTP, les flux d'authentification et les entrées-sorties asynchrones. La plupart des ingénieurs capables d'écrire des applications React complexes s'en sortent sans difficulté.
La frontière de propriété, c'est le BFF lui-même. Il appelle des services en aval, mais il ne possède jamais de logique métier. Les écritures en base, les règles du domaine et les invariants restent dans le backend. Le BFF est résolument dans la couche de présentation. Cette distinction doit être entretenue activement.
Oui, il compte, surtout quand les ingénieurs frontend en sont les principaux propriétaires.
TypeScript avec un runtime Node.js est ma recommandation par défaut. La justification est pratique.
Go est une alternative raisonnable quand votre organisation possède déjà l'expertise Go et que le BFF doit être maintenu par des ingénieurs full-stack ou backend. Go gère bien le fan-out concurrent, ce qui compte quand un BFF lance plusieurs requêtes en aval en même temps pour construire une seule réponse.
Le choix du langage doit suivre le modèle de propriété. Si les ingénieurs frontend possèdent le BFF, ils doivent utiliser le langage qu'ils connaissent déjà. Tout le reste crée de la friction, et la friction finit par repousser la propriété vers les équipes backend.
La sécurité est l'un des arguments les plus forts en faveur d'un BFF, et il est constamment sous-estimé.
Les applications à page unique ont un dilemme bien connu. Où stocker le jeton d'accès ? localStorage est pratique, mais exposé à tout JavaScript de la page. C'est un risque réel à l'ère des attaques sur la chaîne d'approvisionnement des paquets npm. La mémoire est plus sûre, mais elle se vide au rafraîchissement. Les cookies HTTP-only fonctionnent, et ils exigent un traitement côté serveur.
Un BFF règle ce problème complètement. Le navigateur ne détient jamais de jeton d'accès brut.
Le BFF fait l'échange de jetons. Il reçoit le code d'autorisation de votre fournisseur d'identité, l'échange contre un jeton d'accès et un jeton de rafraîchissement, stocke les jetons côté serveur et remet au navigateur un cookie de session HTTP-only. Chaque requête suivante du frontend porte ce cookie. Le BFF retrouve la session, attache le vrai jeton à la requête en aval et la transmet. Le jeton ne passe jamais par le monde JavaScript.
// Le BFF gère le callback OAuth — le navigateur ne voit jamais le token d'accès
app.get("/auth/callback", async (req, res) => {
const { code } = req.query;
const tokens = await identityProvider.exchangeCode(code);
// Les tokens vivent côté serveur
await sessionStore.save(req.sessionId, tokens);
res.setCookie("session", req.sessionId, {
httpOnly: true,
secure: true,
sameSite: "strict",
});
res.redirect("/dashboard");
});Un BFF ne doit jamais devenir un endroit où s'accumulent les règles du domaine. Gardez-le pour ce pour quoi il est fait. L'agrégation en lecture, la forme des réponses et la délégation de l'authentification.
Concrètement :
Si votre BFF a sa propre base de données et commence à posséder des règles métier, vous n'avez plus un BFF. Vous avez un deuxième backend. C'est un problème tout différent.
Si vous utilisez Next.js, vous avez déjà l'infrastructure d'un BFF léger. Ce sont les Route Handlers (App Router) ou les API Routes (Pages Router). Votre frontend et votre BFF vivent dans le même projet et se déploient ensemble, comme une seule unité.
C'est le bon choix par défaut pour les applications petites et moyennes. La contrepartie est que vous ne pouvez pas faire évoluer le BFF indépendamment du frontend. Pour la plupart des équipes, cette contrainte est sans conséquence. Commencez ici.
Pour les applications plus vastes, un motif courant est un service BFF dédié qui vit à côté du frontend dans un monorepo. Les deux partagent les types TypeScript, mais se déploient indépendamment. Le frontend appelle le BFF en HTTP. Le BFF se déploie en éventail vers les services backend.
Cette configuration est plus complexe sur le plan opérationnel, mais elle donne une mise à l'échelle indépendante et une séparation plus propre. Elle a du sens dès que le BFF a assez de complexité pour justifier la surcharge d'exploitation. Cela signifie plusieurs motifs d'agrégation, des couches de cache et du pooling de connexions.
Dans un environnement Kubernetes, le BFF peut être déployé comme conteneur sidecar dans le même pod que le serveur frontend. Le frontend communique avec le BFF via localhost, ce qui maintient une latence quasi nulle, sans saut réseau externe. Ils se déploient toujours ensemble, ce qui élimine tout décalage de version entre les deux.
Ne partagez pas un BFF entre plusieurs applications frontend. Dès que vous le faites, vous construisez une passerelle API. Vous avez réintroduit le même problème de généralisation que vous vouliez résoudre, une couche plus haut.
SoundCloud est l'endroit où le pattern a été nommé. Leur équipe d'ingénierie faisait face exactement au problème de coordination décrit plus haut. Les clients mobile et web avaient besoin de formes de données différentes, et une API générique partagée imposait une négociation constante entre les équipes frontend et backend. La séparation en BFF par client a restauré la vitesse de livraison.
Netflix fait tourner une couche BFF pour chacun des appareils qu'il prend en charge. Il y en a beaucoup. Les téléviseurs, les téléphones, les tablettes, les consoles de jeux et les navigateurs web ont des budgets de performance et des contraintes de rendu sensiblement différents. Une seule API ne peut pas les servir tous correctement. La couche BFF de Netflix gère la traduction pour chaque surface.
Spotify utilise un modèle similaire. Le lecteur web, le client de bureau et les applications mobiles ont chacun des besoins de données et des objectifs de performance différents. Leur couche BFF gère l'éventail vers les services internes et renvoie des réponses taillées pour chaque client.
Dans les trois cas, le BFF est une décision structurelle qui reflète l'organisation des équipes d'ingénierie et la vitesse à laquelle elles doivent livrer.
Le pattern BFF ajoute un saut réseau, un service à exploiter et un déploiement à gérer. Cette surcharge doit mériter sa place.
Faites l'impasse sur le BFF si :
Le BFF le plus coûteux est celui que vous construisez avant d'avoir une raison de le faire. L'échafaudage ajoute une charge cognitive, et sans un vrai problème de coordination à absorber, il devient du code supplémentaire à maintenir.
Le pattern BFF est un outil de structure d'équipe déguisé en motif d'architecture.
Il dit que l'équipe qui construit l'interface doit posséder le contrat API qui l'alimente. Le choix du langage (TypeScript) découle de la propriété. Le modèle de sécurité (jetons conservés côté serveur) découle du fait que le BFF est une couche backend de confiance. Le déploiement découle du fait que le frontend et le BFF évoluent ensemble.
Utilisez-le quand vous avez un problème de coordination. C'est-à-dire quand les ingénieurs frontend attendent que les équipes backend remodelent des réponses, ou quand vous servez plusieurs surfaces client avec des besoins nettement différents. Ne l'utilisez pas pour résoudre un problème que vous n'avez pas encore.
Quand vous en avez besoin, rien ne vaut le BFF pour donner aux équipes frontend un contrôle total sur le contrat entre leur code et les données qu'il consomme.
© Melvin Laplanche - All rights reserved.