Falsafa
FrontendHigh-Level Design

High-Level Design

Architecture overview of Falsafa's frontend: how auth, storage, chat, payments, and the API layer fit together.

The frontend is organized into five subsystems that communicate with Supabase, Stripe, and the Python backend. Each subsystem is covered in detail in the pages that follow. This page shows how they connect.

Subsystem Map

What Each Subsystem Owns

SubsystemResponsibilityData store
AuthUser login, registration, session cookies, role checks, blocked-user enforcementSupabase Auth + profiles + user_roles tables
API LayerAll CRUD operations, backend proxying, file upload orchestrationSupabase DB + Storage, Stripe, Python backend
StorageBook covers, book files, profile images, category imagesSupabase Storage buckets
ChatSession creation, message history, SSE streaming, user preferencesSupabase DB (sessions + messages), Python backend (LLM)
PaymentsCheckout flow, payment intent creation, webhook processing, library assignmentStripe, Supabase DB (purchases + user_library)
AdminStats dashboard, content moderation, user management, system settingsSupabase DB (via service-role client)

Key Design Patterns

  • SSR-first, then client. Pages render on the server with Supabase session cookies. Client components add interactivity on top.
  • Service-role client for privileged operations. getAdminClient() creates a Supabase client with the service-role key, bypassing RLS. Used for admin CRUD, book upload, and role checks - anything that the authenticated user's row-level permissions would otherwise restrict.
  • Debug mode escape hatches. When NEXT_PUBLIC_DEBUG_MODE=true, every auth check, Stripe call, and Supabase initialization can be bypassed with mock data. All pages remain navigable without external services running.
  • API routes as the integration layer. Every external service call (Supabase writes, Stripe API, backend proxying) goes through a Next.js API route. Pages never call external services directly. This keeps the trust boundary at the API layer.
  • Snake-case at the boundary. Supabase stores data in snake_case columns. The TypeScript types in types/index.ts use camelCase. Conversion happens in lib/utils/transform.ts and is applied selectively by each DB query module.

On this page