TravelGo
Full-stack travel booking platform with Google OAuth, JWT, seat selection, and a Gemini-powered travel assistant.
Problem
Bus booking portals in India are slow, cluttered, and rarely offer contextual travel guidance. TravelGo is a lean alternative: search, pick seats, book, and get AI-assisted travel recommendations in one flow.
Impact
- 76 automated unit + integration tests
- 91%+ code coverage
- Load-tested with k6 under concurrent traffic
- Role-based access control across users and operators
Architecture
flowchart LR U[User] --> W[React SPA] W -->|OAuth 2.0 / JWT| A[Auth Middleware] A --> API[REST API] API --> DB[(PostgreSQL via Supabase)] W -->|chat| G[Gemini 1.5 Flash] API --> Seats[Seat Locking Service]
Mermaid flowchart source — renders as a diagram when embedded in docs.
Data model
- users (id, email, role, created_at)
- operators (id, name, verified)
- routes (id, origin, destination, distance_km)
- trips (id, route_id, operator_id, departs_at, price)
- seats (id, trip_id, code, status)
- bookings (id, user_id, trip_id, seat_ids[], status, total)
- payments (id, booking_id, provider, status)
API design
| Method | Path | Purpose |
|---|---|---|
| GET | /api/trips?from&to&date | Search trips |
| GET | /api/trips/:id/seats | Seat map for a trip |
| POST | /api/bookings | Create a booking (protected) |
| GET | /api/bookings/me | Current user's bookings |
| POST | /api/assistant/chat | Context-aware Gemini chat |
Features
- →Google OAuth 2.0 sign-in with JWT session issuance
- →Trip search with origin/destination/date filters
- →Live seat map with optimistic locking on selection
- →Role-based routes for users, operators, and admins
- →AI travel assistant grounded in the user's trip context
Engineering decisions & tradeoffs
Supabase over self-hosted Postgres
Trades vendor lock-in for a faster path to production auth, RLS, and hosted Postgres. Schema is portable, so migrating away later is a database export, not a rewrite.
JWT alongside OAuth
Google handles identity; the API issues its own short-lived JWTs so role claims and revocation are owned by the app, not the identity provider.
Tests as design pressure
Writing 76 tests up front forced small, pure modules — the seat-locking logic and booking state machine ended up isolated and easy to reason about.
Gemini 1.5 Flash over larger models
Flash is fast enough for interactive chat and cheap enough to keep free-tier friendly. Latency and cost mattered more than reasoning depth for travel Q&A.
Challenges & lessons
- — Preventing double-booking of the same seat under concurrent traffic — solved with a short-lived hold + transactional confirm.
- — Keeping the Gemini assistant grounded in the user's live trip context without leaking other users' data.
- — Shaping RLS policies so operators only see their own trips while admins can see everything.
Future improvements
- — Payments integration with idempotent webhooks
- — Push notifications for departures and cancellations
- — Move seat-locking to Redis for lower latency at scale