Added a BFF layer before reaching for GraphQL. What happened next?
A React app originally made dozens of network calls to load a bills worklist, pulling all data into the browser. By adding a lightweight Backend‑for‑Frontend (BFF) layer, the team consolidated requests into a single server‑side call per screen, improved performance, and caught a cross‑user data lea…
When a React worklist page needed to display a bill, its summary, and pending approvers, the front‑end made three separate API calls for each row. The result was a cascade of network traffic: one request to fetch all bills, one for a summary strip, and a separate request for each bill’s approvers. With 9,999 rows, the browser sent over 50 requests before the table could be rendered.
Why the Original Approach Failed
The architecture was a classic “React talks straight to the back‑end” pattern. Each screen had its own logic to join data, which led to duplicated code and drifted implementations. The UI performed filtering, sorting, and pagination in the browser, meaning the full dataset was downloaded even when only a handful of rows were visible. This caused three key problems:
- N+1 Requests – Each visible row triggered an extra call for approvers.
- Data Over‑fetching – The entire bill corpus was sent to the client, wasting bandwidth.
- Inconsistent Join Logic – Every page re‑implemented the same “bill‑to‑approver” join, increasing maintenance risk.
Introducing the BFF Layer
The team added a Backend‑for‑Frontend (BFF) on the existing Express server that already handled server‑side rendering. The BFF exposes one route per screen, each responsible for gathering all data needed for that view. For the payables worklist, the BFF performs the following steps:
- Validates the user’s permission to read bills.
- Creates a server‑side API client that uses the user’s token.
- Fetches all bills with their approvals and vendor details in a single call.
- Applies filtering, sorting, and pagination on the server.
- Returns a validated JSON contract containing only the rows needed for the current page.
From the browser’s perspective, the change is simple: one GET request to /bff/worklist now returns ten rows, total counts, and per‑tab statistics. The heavy lifting is done on the server, eliminating the 50‑plus network round‑trips that previously existed.
Security and Caching Lessons
Adding a BFF introduced two new caching layers that required careful handling. The browser’s HTTP cache and an in‑memory server cache both needed to respect user boundaries. A misconfiguration caused a logged‑out user’s data to be served to a new user for ten seconds. The fix was straightforward: add a Vary: Cookie header to all BFF responses and ensure the server cache keys are scoped by userId. The team also implemented a guard pattern where every loader begins with requirePermission(), ensuring tenant scoping never comes from a query parameter.
Beyond the Worklist: The BFF’s Growing Role
Once the BFF proved its value, other features gravitated toward it. It became the single source for server‑side filtering, sorting, and pagination across nine features, with 80 routes in total. The BFF also handles CSV exports, search debouncing, tab prefetching, and even a durable inbox for notifications using SQLite. Additional unexpected benefits include upload proxying with async job status endpoints and a central attachment safety check that blocks unscanned files from being served.
When deciding whether to add a BFF, ask yourself:
- Does a screen need data from multiple endpoints?
- Is the browser performing joins or full‑dataset pagination?
- Are auth, tenant scoping, or cache rules duplicated across pages?
If the answer is yes to two of these, a BFF can streamline development, improve performance, and enhance security without introducing new infrastructure.
Key Takeaways
- Consolidating API calls into a single BFF route can cut network traffic dramatically.
- Server‑side filtering and pagination reduce client‑side load and improve UX.
- Proper cache headers and key scoping are essential to prevent data leaks.
- Once in place, a BFF naturally becomes the hub for related features like CSV export and notifications.
- Unexpected benefits include upload proxying and centralized attachment safety checks.
Why it matters
By moving data orchestration to a BFF, teams can reduce network overhead, enforce consistent security checks, and centralize cross‑feature logic—leading to faster, safer applications.
Key points
- Reduced dozens of API calls to one per screen
- Server‑side filtering and pagination improve performance
- Added security by scoping caches per user
- BFF became central hub for CSV, notifications, uploads
- Caught a cross‑user data leak before release
Frequently asked questions
What is a BFF?
A Backend‑for‑Frontend is a lightweight server layer that aggregates data from multiple services and tailors responses to the needs of a specific front‑end.
Why not use GraphQL?
GraphQL solves data shape, but the BFF also handles auth, tenant scoping, and caching—functions that GraphQL alone doesn’t provide.
How many routes does the BFF have?
The BFF currently exposes 80 routes across nine features.



