My App

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, schema

API 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_id filter 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() from server/lib/db.ts — service role client for server code
  • createClient() from lib/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:3000 or {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

lib/nav-config.ts defines:

  • primaryNavItems — For You, Mentorship, Courses
  • secondaryNavItems — Resources, Events
  • getCreatorNavItems(creatorId) — Creator console nav
  • getWorkspaceNavItems() — 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

On this page