Application Architecture
HomeThis project demonstrates a clear split: TypeScript for the product UI and session handling, Go for authoritative booking logic and JWT verification, Supabase for Postgres + Auth.
- Identity: Supabase Auth issues JWTs; Next keeps the session; Go verifies tokens with OIDC.
- Data: Single Postgres (Supabase); Go connects with DATABASE_URL (service/pooler role — never expose in the browser).
- Deploy pattern: Next on Vercel (typical) + Go on Railway (long-lived) + CORS restricted to the web origin.
- See the Mermaid system diagram on this page (#system-architecture); source lives in apps/web/src/content/system-architecture.mmd.ts.
access_token as a Bearer JWT, verified against the Auth issuer (JWKS). Observability in this diagram represents metrics scrape/query flow (Prometheus/Grafana), not centralized logs.Source: apps/web/src/content/system-architecture.mmd.ts
Source: apps/web/src/content/deployment-architecture.mmd.ts
apps/api/cmd/server/main.go.| Method | Path | Auth | Handler |
|---|---|---|---|
| GET | /health | none | API.Healthapps/api/internal/handlers/handlers.go |
| GET | /api/v1/db-status | Bearer (Supabase access_token) | API.DBStatusapps/api/internal/handlers/handlers.go |
| GET | /api/v1/availability | Bearer (Supabase access_token) | API.Availabilityapps/api/internal/handlers/handlers.go |
| GET | /api/v1/reservations | Bearer (Supabase access_token) | API.ListMyReservationsapps/api/internal/handlers/handlers.go |
| POST | /api/v1/reservations | Bearer (Supabase access_token) | API.CreateReservationapps/api/internal/handlers/handlers.go |
| POST | /api/v1/slots | Bearer (Supabase access_token) | API.CreateSlotapps/api/internal/handlers/handlers.go |
| POST | /api/v1/benchmark/booking-rush | Bearer (Supabase access_token) | API.BenchmarkBookingRushapps/api/internal/handlers/benchmark.go |
| POST | /api/v1/mimic/notification/email | Bearer (Supabase access_token) | API.MimicEmailPostapps/api/internal/handlers/mimic.go |
| POST | /api/v1/mimic/notification/whatsapp | Bearer (Supabase access_token) | API.MimicWhatsAppPostapps/api/internal/handlers/mimic.go |
db/migrations/- public.resources — bookable entity.
- public.slots — UNIQUE (resource_id, starts_at) defines discrete windows.
- public.reservations — UNIQUE (slot_id) ensures at most one confirmed row per slot (ON CONFLICT target).
- reservations.user_id REFERENCES auth.users(id) — aligns with JWT sub.
- apps/api: Dockerfile; set DATABASE_URL, SUPABASE_URL (or SUPABASE_JWT_ISSUER), CORS_ALLOWED_ORIGINS.
- apps/web: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_API_URL.
- See DEPLOY.md at repo root for Supabase redirect URLs and local dev commands.
The UI is a Next.js frontend; the Go binary is a stateless HTTP API. Supabase provides Postgres + Auth so identity and booking data stay in one database.
- apps/web — App Router, Supabase browser client, fetch wrapper with Bearer token.
- apps/api — chi, CORS, OIDC JWT verification, pgxpool, transactional handlers.
- db/migrations — DDL (resources, slots, reservations) applied to Supabase Postgres.
Go
apps/api/cmd/server/main.goWires chi middleware, CORS, /health, and /api/v1 routes with JWT middleware on protected handlers.
TypeScript / Next
apps/web/src/lib/api.tsAttaches Authorization: Bearer for every call to the Go base URL.
This app uses @supabase/supabase-js with the anon key in the browser only. Email/password (and sign-up with email confirmation) talk to Supabase Auth — not to the Go API.
- signInWithPassword / signUp set a session; cookies are maintained for SSR via @supabase/ssr.
- Go never receives the anon key. Protected API calls send only the user’s access_token as Bearer.
TypeScript / Next
apps/web/src/lib/supabase/client.ts — createClientBrowser Supabase client (anon key).apps/web/src/app/login/page.tsxsignInWithPassword after the user has an account.apps/web/src/app/signup/page.tsxsignUp with emailRedirectTo → /auth/callback?next=…
The UI calls GET /api/v1/availability with the Supabase access token. The handler returns future slots for a resource that have no confirmed reservation (LEFT JOIN + r.id IS NULL).
- handlers.Availability validates resource_id, pgxpool.Query with timestamptz scan → RFC3339 strings in JSON.
- Client: apiFetchJson in apps/web/src/lib/api.ts with Bearer from supabase.auth.getSession().
Go
apps/api/internal/handlers/handlers.go — AvailabilitySQL selects open slots; limit 200.
TypeScript / Next
apps/web/src/app/book/page.tsxload() reads session, then GET availability for NEXT_PUBLIC_DEFAULT_RESOURCE_ID or demo UUID.