Skip to main content

Tech stack

LayerTechnology
FrontendNext.js 16 (App Router, Turbopack), React 19, Tailwind CSS v4
UI componentsshadcn/ui v4 (base-nova style)
APIFastify 5, TypeScript
DatabaseNeon serverless Postgres, Drizzle ORM
AuthClerk (organizations enabled)
BillingStripe
ValidationZod
AIOpenAI GPT-4

Monorepo structure

PoolPuma uses a pnpm workspaces + Turborepo monorepo:
  • apps/web: Next.js frontend with App Router
  • apps/api: Fastify REST API
  • apps/docs: This documentation site (Mintlify)
  • packages/db: Shared Drizzle ORM schema and database utilities

Multi-tenancy

PoolPuma is multi-tenant using Clerk organizations. Each organization represents a pool service company and can have multiple stores (service regions).
Clerk org and member data is synced to the database on first access via the ensureOrgSynced() utility. This avoids webhook-only reliance and handles edge cases like missing webhook deliveries.

Database schema

The database has 18 tables organized around these domains:
  • Tenancy: organizations, stores, memberships, store_access
  • CRM: customers, properties, pool_profiles
  • Operations: routes, route_stops, technician_assignments, service_visits, service_reports
  • Billing: plans, subscriptions, invoices
  • Inventory: inventory_items, equipment_assets
  • System: audit_events

Authentication flow

  1. User signs in via Clerk
  2. Clerk middleware protects all /dashboard routes
  3. On first dashboard access, ensureOrgSynced() syncs the Clerk org and members to the database
  4. All subsequent queries are scoped to the user’s organization