Skip to content
CASE STUDY

Go Elite

On-demand bodyguard booking · Flutter · NestJS · Next.js

NestJS · Next.js 15 · Flutter · TypeScript · PostgreSQL · Prisma · Redis · BullMQ · Socket.IO · Stripe · Firebase · Docker
Go Elite mobile app booking flow
Go Elite admin dashboard

Problem

Hiring close-protection security is traditionally slow, opaque, and relationship-driven — clients call agencies, wait for quotes, and have no visibility into who is actually showing up. Bouncers, on the other side, have no consistent platform to find gigs, build a reputation, or get paid reliably. Go Elite set out to be the "Uber for personal security": instant booking, transparent pricing tied to bouncer tier, live ETA tracking, and automatic payouts after a job.

Approach

  • 01

    Split the NestJS backend into ~25 domain modules (bookings, bouncer-level, payout, transactions, reviews, push-notification, etc.) so each concern — Stripe webhooks, Firebase auth, email queues — stays isolated and testable.

  • 02

    Used Prisma + PostgreSQL as the source of truth for user, booking, and financial data, with migrations versioned in the repo. Retained Mongoose for chat and notification documents where a flexible schema actually paid for itself.

  • 03

    Pushed outbound email and notification fan-out onto BullMQ workers on Redis so user-facing requests never block on SMTP or FCM latency.

  • 04

    Built a Socket.IO gateway for live booking status and bouncer location pings, paired with FCM so push still landed when the app was closed.

  • 05

    Shipped a single Flutter binary with role-aware navigation — client_home and bouncer_home share the same auth and chat stack but diverge into role-specific booking flows, with Riverpod for state and flutter_map + Geolocator for live tracking.

Outcome

  • 01

    Shipped three coordinated surfaces — API, admin dashboard, and mobile app — from a single codebase owner.

  • 02

    Booking lifecycle is fully automated: request → bouncer assignment → live tracking → payment capture → payout, with no manual ops step in the middle.

  • 03

    Admin team can vet bouncers, refund bookings, and monitor revenue from one Next.js dashboard instead of querying the database directly.

  • 04

    Stripe and Firebase incidents stayed contained to the queue layer — no user-facing 500s during third-party outages.

Lessons

  • 01

    Do not split data stores until you have to. Running Postgres (Prisma) and MongoDB (Mongoose) in the same service doubled the operational surface area — starting over, I would keep everything in Postgres until a real document-store workload appeared.

  • 02

    Queue everything that touches a third party. Putting email and FCM behind BullMQ from day one meant Stripe/Firebase blips never cascaded into user-facing failures.

  • 03

    Two-sided Flutter apps want one binary, not two. Sharing the codebase between client and bouncer roles with role gating at the navigation layer saved months versus shipping two apps, even though the home screens look completely different.