Skip to content
Platform · 2025 — Present

Backend-for-Frontend Layer

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

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

Prove it

Try it live

Real requests against real (artificially-delayed) simulated services.

ClientBFFCustomerEligibilityOffersMerchantPolicyLimits
—
Client round trips
—
Total wall-clock
—
Response payload
Real requests, real measured latency — against simulated services built for this demo, not production traffic. Every run re-randomizes the delay, so the numbers differ each time.

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
Always up for a good problem

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.