Backend for Frontend skill for Claude Code
Add a BFF or gateway only for a named problem. Claude starts by measuring calls per screen and latency before agreeing to a new layer, then splits duties cleanly: gateways take TLS, edge auth and rate limits, while a client-owned BFF aggregates and shapes data per screen. Aggregation runs in parallel under deadlines with explicit degraded fields, browser tokens stay server-side, and business rules stay in the services that own the data.
- name
backend-for-frontend - size169 lines · ≈2,100 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Decides whether a client needs a backend-for-frontend, an API gateway, both, or neither, and designs the chosen layer - per-client aggregation with parallel fan-out, deadlines and partial responses, response shaping, token handling for browser apps with server-side sessions, caching, mobile version compatibility, ownership, and the anti-patterns that turn a BFF into a second monolith. Use when a web or mobile client makes too many API calls per screen, frontend teams wait on backend changes, browser apps hold access tokens, or someone proposes adding a gateway or BFF layer. Try asking
- Our mobile home screen makes 11 API calls. Should we add a BFF?
- Design a BFF endpoint for the order detail page with partial failure handling
- Move our SPA's access tokens out of localStorage using a BFF
Use it when
- A client makes many calls per screen or over-fetches badly
- Someone proposes an API gateway or BFF layer
- A browser app stores OAuth tokens in JavaScript-readable storage
- Web and mobile need different response shapes and release cycles
Skip it when
- You are deciding how to split services themselves — use service-boundaries
- The client should query a unified graph — use graphql-schema
- You need timeouts and circuit breakers in general — use resilience-patterns
How to check the result
Each endpoint meets its p95 budget under load, returns within its deadline with a degraded flag when an optional dependency hangs, and no tokens reach browser JavaScript.
The common mistake it prevents
Letting business rules creep into a BFF until web and mobile BFFs disagree on prices or eligibility because each re-implemented the rule.
Done well means: every added layer has a named job that no existing component does, gateways handle cross-cutting edge concerns while BFFs handle one client’s experience, neither holds business rules or owns data, and each screen’s latency budget is met even when one downstream service is slow or down.
Decide whether to add a layer
| Symptom | Likely answer |
|---|---|
| One web client, a few services, screens need 1 to 3 calls | No new layer. Improve the APIs |
| Many services exposed to the internet, each re-implementing TLS, auth, rate limits | API gateway (managed or Envoy/Kong/NGINX) |
| Screens need 6+ calls, over-fetching, chatty mobile traffic on bad networks | BFF for that client |
| Web and mobile need very different shapes and release cadences | One BFF per client experience |
| SPA stores OAuth access tokens in the browser | BFF as token holder (server-side session, cookie to browser) |
| Many teams contributing fields to one graph | GraphQL gateway/federation instead of per-team BFFs |
| “A layer for future flexibility” with no current symptom | No new layer |
Each layer adds a network hop (1 to 5 ms in-region), a deploy, an on-call surface, and a place for logic to hide. Write the problem it solves in one sentence before designing it.
Responsibilities
| Concern | Gateway | BFF | Downstream service |
|---|---|---|---|
| TLS termination, WAF, IP allow/deny | Yes | ||
| Coarse rate limiting and quotas | Yes | Per-user limits if needed | |
| Authentication (validate token or session) | Yes, or BFF | Yes for browser sessions | Validates propagated identity |
| Authorization of business actions | Yes, always | ||
| Routing and API versioning by path | Yes | ||
| Aggregating several services for one screen | Yes | ||
| Shaping, renaming, trimming fields for a client | Yes | ||
| Business rules, validation of domain invariants | Never | Yes | |
| Owning data of record | Never | Never (cache only) | Yes |
Downstream services must still authorize every request. A BFF in front does not make internal endpoints safe to expose without checks.
Workflow
- [ ] 1. Measure: calls per screen, payload sizes, p95 latency per call, failure rates
- [ ] 2. Decide gateway / BFF / neither with the table above; record why
- [ ] 3. Assign ownership (the client team owns its BFF)
- [ ] 4. Design endpoints per screen or use case, not per downstream service
- [ ] 5. Set deadlines, fan-out, and degradation rules per endpoint
- [ ] 6. Design auth and token flow
- [ ] 7. Plan versioning for old clients and the deploy pipeline
- [ ] 8. Verify the latency budget and failure behavior
4. Endpoint design
- Name endpoints after client use cases:
GET /bff/home,GET /bff/orders/{id}/detail. - Return exactly what the screen renders, already formatted where formatting is client-specific (display strings, image URLs sized for the device).
- Keep generic, reusable operations (create order, update profile) as thin passthroughs to the owning service; do not re-implement them.
5. Aggregation, deadlines, degradation
// GET /bff/orders/:id/detail — budget 300 ms at p95
export async function orderDetail(req: Request, orderId: string) {
const deadline = AbortSignal.timeout(280); // leave margin for serialization
const ctx = { headers: forwardHeaders(req), signal: deadline }; // trace + user identity
const [order, shipment, recs] = await Promise.allSettled([
ordersApi.get(orderId, ctx), // critical
shippingApi.byOrder(orderId, { ...ctx, signal: AbortSignal.any([deadline, AbortSignal.timeout(150)]) }),
recsApi.forOrder(orderId, { ...ctx, signal: AbortSignal.any([deadline, AbortSignal.timeout(100)]) }),
]);
if (order.status === "rejected") throw toProblem(order.reason); // no order, no page
return {
order: shapeOrder(order.value),
shipment: shipment.status === "fulfilled" ? shapeShipment(shipment.value) : null,
recommendations: recs.status === "fulfilled" ? recs.value.slice(0, 6) : [],
degraded: [shipment, recs].some((r) => r.status === "rejected"),
};
}
Rules:
- Classify each downstream call as critical (fail the response) or optional (degrade to
null/empty with an explicit
degradedflag or per-field status). - Fan out in parallel. Sequential calls only where one result feeds the next; each sequential hop must fit inside the remaining deadline.
- Per-call timeouts shorter than the endpoint deadline; propagate the remaining deadline
downstream (gRPC deadlines or a header such as
X-Request-Deadline). - No retries inside the BFF for user-facing reads unless the budget allows one fast retry; retries multiply load during incidents.
- Avoid N+1 fan-out (one call per list item). Use batch endpoints downstream or a DataLoader-style batcher per request.
- Cache shared, non-personal data (catalog, config) in the BFF with short TTLs; never cache per-user responses in a shared cache without the user in the key.
6. Auth and tokens
For browser apps, the BFF pattern is the recommended way to keep OAuth tokens out of JavaScript:
- BFF runs the OAuth authorization code flow as a confidential client, stores access and
refresh tokens server-side (session store), and gives the browser a session cookie:
__Host-session=...; Secure; HttpOnly; SameSite=Lax; Path=/. - Protect state-changing requests against CSRF: SameSite plus a CSRF token or a required custom header checked by the BFF.
- The BFF attaches the access token when calling downstream APIs, or performs a token exchange for a downstream-specific audience.
- Mobile apps can hold tokens securely in the platform keystore; a mobile BFF then validates bearer tokens like any API.
7. Versioning and releases
- Web BFFs deploy together with the web app; breaking changes are fine when both ship atomically.
- Mobile BFFs must serve every app version still installed. Read the app version from a
header (
X-App-Version), keep responses backward compatible, and track version share before removing old shapes. Plan for 6 to 18 months of old clients. - Contract tests between the client and its BFF (consumer-driven) catch shape breaks before deploy.
Anti-patterns
| Anti-pattern | Symptom | Fix |
|---|---|---|
| One shared “BFF” for every client | Endpoints with ?client=ios switches, conflicting changes | One BFF per experience, or move shared logic into services |
| Business logic in the BFF | Pricing or eligibility rules duplicated between web and mobile BFFs | Move the rule to the owning service |
| BFF owns a database of record | Data only reachable through the BFF | Data belongs to a domain service; BFF caches at most |
| BFF calls another BFF | Layered latency, unclear ownership | Call domain services directly |
| Gateway with request transformation scripts per endpoint | Logic hidden in gateway config, untested | Move shaping to a BFF or the service |
| Exposing internal services through the gateway without auth | “Internal” endpoints reachable from the internet | Authenticate at the edge and authorize in services |
Verification
- Latency budget: for each endpoint, sum the critical path (slowest parallel group plus sequential hops plus BFF overhead) and confirm p95 fits; load-test it.
- Failure test: make each optional downstream return errors or hang (fault injection,
toxiproxy, or a stub); the endpoint returns within its deadline with
degraded: true. - Make the critical downstream fail: the endpoint returns a problem response quickly, without retrying in a loop.
- Browser apps: no access or refresh token appears in JavaScript-readable storage or
responses; cookies carry
Secure,HttpOnlyandSameSite. - Calls per screen and bytes per screen dropped against the baseline from step 1.
Output contract
## Decision
Problem statement, layer chosen (gateway / BFF / none), alternatives rejected
## Ownership
Team, repo, on-call
## Endpoints
| Endpoint | Screen | Downstream calls (critical/optional) | Deadline | Cache |
## Auth flow
Diagram or steps; where tokens live
## Versioning
Client version policy, contract tests
## Failure behavior
Per endpoint: what the client sees when each dependency fails About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.