Architecture No. 311

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.

  • namebackend-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

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.

~/.claude/skills/backend-for-frontend/SKILL.md

--- name: backend-for-frontend description: Decides whether a client needs a backend-for-frontend, an API gateway, both, or neither, and designs the chosen layer - per-client… license: MIT ---

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

SymptomLikely answer
One web client, a few services, screens need 1 to 3 callsNo new layer. Improve the APIs
Many services exposed to the internet, each re-implementing TLS, auth, rate limitsAPI gateway (managed or Envoy/Kong/NGINX)
Screens need 6+ calls, over-fetching, chatty mobile traffic on bad networksBFF for that client
Web and mobile need very different shapes and release cadencesOne BFF per client experience
SPA stores OAuth access tokens in the browserBFF as token holder (server-side session, cookie to browser)
Many teams contributing fields to one graphGraphQL gateway/federation instead of per-team BFFs
“A layer for future flexibility” with no current symptomNo 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

ConcernGatewayBFFDownstream service
TLS termination, WAF, IP allow/denyYes
Coarse rate limiting and quotasYesPer-user limits if needed
Authentication (validate token or session)Yes, or BFFYes for browser sessionsValidates propagated identity
Authorization of business actionsYes, always
Routing and API versioning by pathYes
Aggregating several services for one screenYes
Shaping, renaming, trimming fields for a clientYes
Business rules, validation of domain invariantsNeverYes
Owning data of recordNeverNever (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 degraded flag 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-patternSymptomFix
One shared “BFF” for every clientEndpoints with ?client=ios switches, conflicting changesOne BFF per experience, or move shared logic into services
Business logic in the BFFPricing or eligibility rules duplicated between web and mobile BFFsMove the rule to the owning service
BFF owns a database of recordData only reachable through the BFFData belongs to a domain service; BFF caches at most
BFF calls another BFFLayered latency, unclear ownershipCall domain services directly
Gateway with request transformation scripts per endpointLogic hidden in gateway config, untestedMove shaping to a BFF or the service
Exposing internal services through the gateway without auth“Internal” endpoints reachable from the internetAuthenticate at the edge and authorize in services

Verification

  1. 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.
  2. 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.
  3. Make the critical downstream fail: the endpoint returns a problem response quickly, without retrying in a loop.
  4. Browser apps: no access or refresh token appears in JavaScript-readable storage or responses; cookies carry Secure, HttpOnly and SameSite.
  5. 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.

esc