Context
A salon and spa business ran a points-based customer loyalty program layered on top of their existing POS platform (Vagaro). Customers earned points per visit and redeemed them for free or discounted services. A backend was already live in production, ingesting POS webhooks and tracking point balances in DynamoDB — but there was no way for staff to look up a customer, edit a profile, adjust a balance, or record a redemption, and no frontend at all.
Problem
The staff-facing CRUD API and authentication were fully designed on paper but not implemented — only the webhook-ingestion path existed in production. Two capabilities had no design at all going in: audit logging for staff profile edits, and recording a redemption (a customer spending points on a service or discount).
Approach
Built the missing staff CRUD API and Cognito authentication (staff-only, admin-created accounts) onto the existing serverless backend. Built a mobile-friendly, low-JavaScript staff console as a thin server-side backend-for-frontend: it authenticates against Cognito, holds the session in an httpOnly cookie, and proxies every request to the real API with the bearer token attached server-side — so the browser never handles a JWT directly. The console embeds into the business's existing website via a same-site iframe, keeping the session cookie first-party rather than tripping third-party-cookie blocking. Designed the audit-log and redemption data models from scratch, since neither existed before this work.
Outcome
Staff can now look up a customer, edit their profile, adjust a point balance, or record a redemption from a phone or tablet at the front desk — no AWS console access, no direct database queries, and every change is now logged. The same session-gating pattern was later reused to put the project's internal documentation behind staff login without building a second auth system for it.
Why a server-side BFF
Keeping JavaScript minimal (HTMX, no SPA framework) usually means the browser can’t hold a token securely — there’s no safe place to put it. Routing every request through a thin FastAPI backend-for-frontend solves that: the browser only ever sees an httpOnly session cookie, and the actual bearer token stays server-side. It also meant the staff console could stay genuinely simple — server-rendered HTML with HTMX swaps, not a client-side app shell.
What was already there vs. what wasn’t
The webhook-ingestion path — the part that actually earns customers points on each visit — was live and working. Everything staff-facing was still just a specification. That gap between “the pipe works” and “a human can use it” is a common one for backends built first and fronted later; closing it here meant building real CRUD endpoints, an authorizer, and a UI, not just wiring up a static frontend.
← All projects