Siddhant DevalAuthor
Senior Full-Stack Engineer·Oct 6, 2026·18 min read
BFF Architecture: Client Ownership, Concurrent Aggregation & Edge Runtimes
A Backend-for-Frontend is not a proxy — it is a client-contract-shaped integration layer that translates between what downstream microservices produce and what the client UI needs to render. This article implements a production-grade BFF with Fastify and Hono on the edge, with Promise.allSettled fan-out and circuit-breaker resilience patterns.
Technical Series
Frontend Platform & Scale Architecture
Part 5 of 6
BFF Architecture: Client Ownership, Concurrent Aggregation & Edge Runtimes
Architecture is not about drawing boxes on a whiteboard — it is about enforcing boundary contracts, deterministic caching, and secure data mediation across independent release units. Nowhere is the mediation problem more acute than at the boundary between a browser-based client and a landscape of heterogeneous microservices. The client needs a slim, UI-shaped payload. The microservices produce normalized, domain-shaped data. Between them, someone must translate.
Most teams let the client do this translation: the browser makes five parallel API calls, stitches the responses together, handles the partial failures, and converts gRPC Protobuf responses into JSON. The result is fat clients with complex error-handling logic, five-to-ten round-trip network waterfalls visible in every Lighthouse trace, and frontend teams blocked on microservice API design decisions made for backend consumers.
The Backend-for-Frontend removes this complexity from the client entirely. But a BFF that is designed poorly becomes a centralized bottleneck, a responsibility vacuum, and eventually a backend monolith with a misleading name. This article builds it correctly.
1. The API Gateway vs. BFF Paradigm
1.1 Why a General-Purpose API Gateway Fails Heterogeneous Clients
An API gateway sits in front of all microservices and routes requests. Enterprise gateways like Kong, AWS API Gateway, and NGINX handle authentication, rate limiting, SSL termination, and routing. They are excellent at what they do.
They are the wrong tool for client-tailored data aggregation.
The gateway does not aggregate. It routes. The client either makes four requests (network waterfall) or the team builds custom aggregation logic into the gateway (it is now a BFF in disguise, but owned by a platform team that does not understand the client's requirements).
1.2 The 1-Experience-to-1-BFF Rule
Each client has fundamentally different payload requirements. A mobile client on a 4G connection cannot afford the same response size as a desktop client on broadband. A partner integrator needs backward-compatible versioned responses that a first-party client does not need.
A BFF that serves multiple clients is not a BFF — it is an API gateway with extra steps.
1.3 What a BFF Is Allowed to Do
| Allowed | Forbidden |
|---|---|
| Fan-out aggregation (call multiple services, compose one response) | Business rule computation (pricing algorithms, discount logic) |
| Payload slimming (remove fields the client doesn't need) | Direct database writes (bypasses domain service ownership) |
| Protocol translation (gRPC → JSON, Protobuf → REST) | Cross-cutting state persistence (sessions, feature flags, A/B state) |
| Response shape transformation (rename fields for client conventions) | Business state mutations that should be owned by a domain service |
| Downstream error handling and fallback payloads | Anything that requires a domain expert to understand |
Crucial Requirement
The moment business logic enters a BFF, the BFF has violated its contract. Business rules belong in domain services. The BFF is an integration layer — it knows about clients, not about domains. A BFF that knows about pricing is a checkout backend. A BFF that knows about inventory is an inventory backend. Keep the boundary sharp.
2. Implementing a Production BFF with Fastify
2.1 Why Fastify
| Criterion | Fastify | Express | Hono (edge) |
|---|---|---|---|
| JSON serialization | Fast-json-stringify (2-6x faster) | JSON.stringify (slow) | Standard (streaming) |
| Request validation | Native schema validation (TypeBox/JSON Schema) | Manual or third-party | Native Zod/validator |
| TypeScript support | First-class, typed plugins | Requires types package | First-class |
| Plugin encapsulation | Scoped plugin system (no global state) | Middleware pollution | Middleware-based |
| Request throughput | ~90k req/s | ~60k req/s | ~150k req/s (edge) |
| Deployment target | Node.js (EC2, ECS, Cloud Run) | Node.js | Cloudflare Workers, Vercel Edge |
2.2 BFF Server Setup with TypeBox Validation
typescript
typescript
2.3 Product Page Aggregation Route
typescript
The key design decisions in this implementation:
Promise.allSettledoverPromise.all: Every service call runs concurrently. A failure in the recommendations service does not abort the product or inventory calls.- Criticality tiers: The product service is critical — its failure returns 503. Inventory, recommendations, and wishlist are non-critical — their failures return safe defaults.
- Payload slimming: The BFF maps
product.pricing.basePricetopriceandproduct.images[0]?.urltoimageUrl. The client receives a flat, UI-shaped object, not a domain-normalized nested structure. - Per-service timeout:
AbortSignal.timeout(3000)ensures a hanging inventory service never holds the request past 3 seconds.
3. Concurrent Aggregation & Failure Isolation
3.1 The Promise.all Trap
typescript
typescript
3.2 Circuit Breaking with opossum
Timeout budgets protect against slow services. Circuit breakers protect against services that are failing consistently — preventing the BFF from wasting thread time on requests that will fail anyway.
typescript
When the recommendations service enters a failure cascade, the circuit breaker opens after the threshold is hit. Subsequent calls fast-fail immediately (no network wait) and return the safe default. After the reset timeout, the breaker enters half-open state and tests one request. If it succeeds, the circuit closes.
Architectural Note
The health states of a circuit breaker are: Closed (normal operation), Open (fast-failing all calls), Half-Open (testing recovery with one call). A breaker in Open state eliminates the accumulation of timed-out requests that would otherwise exhaust Node.js's event loop queue in a cascade failure.
4. Ultra-Low-Latency Edge BFF with Hono
4.1 When to Choose Hono Over Fastify
| Requirement | Fastify (Node.js) | Hono (Edge) |
|---|---|---|
| Cold start | ~200–500ms | <5ms |
| Global distribution | Requires multi-region deployment | Automatic (CDN edge nodes) |
| Node.js APIs available | ✅ Full access | ❌ Web Standards only |
| CPU-intensive work | ✅ Worker threads available | ❌ Time-limited execution |
| Redis / database access | ✅ Direct TCP connections | ⚠️ Via HTTP APIs only (no direct TCP) |
| Streaming responses | ✅ Full support | ✅ Native Web Streams |
Hono is the right choice when:
- The BFF handles simple aggregation with no direct TCP database connections.
- Global latency (<5ms response initiation) is a product requirement.
- The team is comfortable with Web Standards APIs (
Request,Response,Headers,URL).
4.2 Product Page BFF in Hono (Cloudflare Workers)
typescript
Performance / Safety Warning
Hono on Cloudflare Workers has no access to Node.js-specific APIs: no
node:crypto, no node:fs, no TCP sockets, no native Node.js https module. If your BFF needs direct Redis access, database TCP connections, or Node.js-specific encryption primitives, deploy on Node.js (Cloud Run, ECS) or use Cloudflare's KV, D1, and Hyperdrive binding APIs.5. Distributed Observability
5.1 W3C traceparent Propagation
Every BFF request originates from a browser. For end-to-end distributed tracing (Datadog, Jaeger, Honeycomb), the trace context must flow: Browser → BFF → Downstream Microservices.
typescript
typescript
With
traceparent flowing through every downstream call, a failed product page request in Datadog shows the complete trace: Browser (0ms) → BFF (12ms) → Product Service (8ms) → Inventory Service (timeout at 3000ms). The slow inventory service is immediately visible without log correlation.
Expand
6. Health Checks & Graceful Shutdown
6.1 Health Check Endpoint
typescript
6.2 Graceful Shutdown
typescript

Expand
Summary
| Concept | Rule |
|---|---|
| 1-BFF-to-1-client | Never serve multiple client types from one BFF — it becomes an API gateway |
Promise.allSettled | Always use over Promise.all for fan-out — classify each result independently |
| Criticality tiers | Critical services (product) → 503 on failure; non-critical (recs, wishlist) → safe defaults |
| Circuit breakers | Open on 50% failure threshold to fast-fail consistently failing services |
| Fastify vs. Hono | Fastify for Node.js with TCP connections; Hono for sub-5ms global edge with Web Standards APIs |
traceparent | Propagate W3C traceparent from browser through BFF to every downstream call |
What's Next
In Part 6, we address the security dimension of the BFF architecture: why browser-based SPAs cannot safely hold OAuth access tokens, and how the BFF acts as an OAuth 2.0 confidential client that keeps raw tokens in server-side session storage, convertinghttpOnlysession cookies into downstreamBearer JWTheaders on every request.
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.
#BFF#Backend-for-Frontend#Fastify#Hono#Edge Runtime#API Architecture#Resilience
Technical Series
Frontend Platform & Scale Architecture
Part 5 of 6