Skip to content
Platform · 2025 — Present

Backend-for-Frontend Layer

One typed call per screen, instead of a client stitching microservices together.

~60%
Lower p95 on aggregated screens
8 → 1
Client round trips per screen
~50%
Smaller response payloads

Overview

The aggregation layer that sits between SwiffyLabs client applications and the core backend. It started as an intern prototype to decouple frontend clients from the microservices behind them, and became the layer every surface goes through. Policy engines and merchant portals get orchestrated behind it; the client receives exactly the data the screen needs and nothing else.

The shape of it

One screen, one request

Rendering a loan-offer screen — same data, two architectures.

GET /screen/loan-offer
customer.Get
eligibility.Evaluate
offers.List
merchant.Get
policy.Resolve
limits.Get
0890
1
Client round trips
175 units
Critical path
Server
Data shaping
HTTP, client-facinggRPC, internal — issued in parallel by the BFF

What it took

  • 01

    Architected and owned the end-to-end BFF layer over gRPC and HTTP microservices, optimising payload structures and cutting response latency.

  • 02

    Designed the initial service prototypes that decoupled frontend clients from core backend microservices — the work that justified the layer existing.

  • 03

    Implemented Keycloak authentication with SSO and MFA across the layer, so every client inherits one auth story.

  • 04

    Defined Swagger API contracts up front, which turned integration from a conversation into a generated client.

  • 05

    Eliminated client-side over-fetching — the frontend became purely presentational, with no data transformation left in it.

Code

screen-resolver.ts
1// One resolver per screen. The client asks for a screen, not for entities.2export async function resolveLoanOfferScreen(ctx: RequestContext) {3  // Fan out over gRPC in parallel — the critical path is the slowest call,4  // not the sum of all of them.5  const [customer, eligibility, offers, merchant, policy] = await Promise.all([6    customerClient.get({ id: ctx.customerId }),7    eligibilityClient.evaluate({ customerId: ctx.customerId }),8    offersClient.list({ customerId: ctx.customerId }),9    merchantClient.get({ id: ctx.merchantId }),10    policyClient.resolve({ product: 'CONSUMER_DURABLES' }),11  ].map(settle));12 13  // Partial failure is a first-class outcome: a degraded card, not a blank page.14  return {15    header: pick(customer, ['displayName', 'maskedMobile']),16    offers: offers.ok ? offers.value.map(toOfferCard) : degraded('offers'),17    eligibility: eligibility.ok ? toBanner(eligibility.value) : degraded('eligibility'),18    merchant: merchant.ok ? toMerchantChip(merchant.value) : null,19    disclosures: policy.ok ? policy.value.disclosures : [],20    traceId: ctx.traceId, // every response carries it — support tickets become waterfalls21  };22}
Illustrative sketch of the pattern, not production source — the SwiffyLabs implementation is private. It shows the shape: one screen resolver, a parallel gRPC fan-out, and a response envelope where a single slow dependency degrades one card instead of the page.
Share
Open to new opportunities

Let's build something worth maintaining

Have a platform problem, a team that needs a hand, or just want to argue about API design? My inbox is open.