Architecture
System architecture, design patterns, and technical overview
Overview
The platform is a multi-tenant SaaS for ecosystem builders (accelerators, incubators) to deliver programmes, courses, events, mentorship, and resources to companies.
Key concepts: Workspace (tenant) → Creators (providers/orgs) → Programmes → Companies/Users. The mentorship system sits across all of these.
Stack
- Next.js 16 (App Router) with React 19
- Supabase (Postgres + Auth + Storage)
- TanStack Query for client-side data fetching
- Tailwind CSS v4 + Shadcn components in
/components/ui(built on Base UI@base-ui/react) - Zod for schema validation
- Biome (via Ultracite) for linting/formatting
Directory Layout
app/ # Next.js App Router
(app)/ # Authenticated app shell (sidebar layout)
(account)/ # Account-scoped pages
api/v1/ # REST API routes
auth/ # Auth flow pages and callbacks
docs/ # Fumadocs-powered documentation
server/ # Server-only code (never imported client-side)
api/ # Endpoint factories, auth helpers, responses
repositories/ # Data access layer (workspace-isolated)
lib/ # Server utilities (audit, events, geocoding, mentorship)
permissions/ # Permission definitions and checks
lib/ # Shared utilities (Supabase client/server, nav config, types)
hooks/queries/ # TanStack Query hooks
components/ # React components by domain
supabase/ # Migrations, docs, schemaAPI Route Pattern
Every API route uses one of two endpoint factories from server/api/:
endpoint()
General-purpose handler (server/api/endpoint.ts):
- Authentication (required or optional)
- Permission check via
server/permissions/definitions.ts - Optional Zod body parsing
- Optional resource loading (workspace-isolated)
- Automatic error handling via
handleError()
export const GET = endpoint({
auth: "required",
permission: "workspace:read:settings",
handler: async ({ user, requestId }) => {
return { data: await something() };
},
});creatorConsoleEndpoint()
For creator/provider console routes (server/api/creator-console.ts):
- Resolves the user's active creator from staff membership
- Enforces creator roles (owner/admin/editor)
Mentorship Routes
Mentorship routes use neither factory — they have custom auth via server/api/mentorship-auth.ts because they require cross-entity context (mentor + mentee + admin simultaneously).
Response Helpers
All responses come from server/api/responses.ts:
ok(),unauthorized(),forbidden(),notFound()validationError(),conflict(),internalError()handleError()for centralized error handling
Repository Pattern
server/repositories/base.ts exports createRepository<T>(tableName, options) — a factory providing record(), list(), create(), update(), remove() with automatic:
- Workspace isolation (
workspace_idfilter on every query) - Audit logging (
writeAuditLog()) - Event emission (
emitEvent())
Mentorship Exception
The mentorship repository (server/repositories/mentors.ts) does NOT use createRepository() because mentorship tables have no workspace_id. Workspace isolation is done via JOIN through mentor_programmes → programmes → creators → workspace_id.
All repositories use getServiceDb() (service role, bypasses RLS).
Database Access
getServiceDb()fromserver/lib/db.ts— service role client for server codecreateClient()fromlib/supabase/server.ts— user-scoped client for auth checks
Auth Context
getUser(req) / getActorContext(req) in server/api/auth.ts returns:
{
userId, email,
workspaceId, // resolved from subdomain or cookie
workspaceRole, // workspace:owner | workspace:admin | workspace:editor | ...
platformRoles, // from user_roles table
mentorId, // if user has a mentor profile
}Multi-Tenancy
- Subdomain-based:
{workspace}.localhost:3000or{workspace}.platform.com - Vercel preview:
{workspace}---{branch}.vercel.app - Root domain: marketing landing; subdomain: tenant app
- Workspace resolved from subdomain or cookie
Permissions
Defined in server/permissions/definitions.ts. Format: resource:action:scope (e.g. workspace:read:settings).
Roles are workspace-scoped (workspace:admin) or entity-scoped (company:founder, authenticated, anonymous).
Client Data Fetching
- TanStack Query hooks live in
hooks/queries/ - They call
/api/v1/endpoints - No direct Supabase calls from client components
Navigation
lib/nav-config.ts defines:
primaryNavItems— For You, Mentorship, CoursessecondaryNavItems— Resources, EventsgetCreatorNavItems(creatorId)— Creator console navgetWorkspaceNavItems()— Workspace admin nav
Commands
npm run dev # Start Next.js dev server
npm run build # Production build
npm exec -- ultracite fix # Format and auto-fix lint issues
npm exec -- ultracite check # Check for issues without fixing