Tech stack
| Layer | Technology |
|---|---|
| Frontend | Next.js 16 (App Router, Turbopack), React 19, Tailwind CSS v4 |
| UI components | shadcn/ui v4 (base-nova style) |
| API | Fastify 5, TypeScript |
| Database | Neon serverless Postgres, Drizzle ORM |
| Auth | Clerk (organizations enabled) |
| Billing | Stripe |
| Validation | Zod |
| AI | OpenAI GPT-4 |
Monorepo structure
PoolPuma uses a pnpm workspaces + Turborepo monorepo:apps/web: Next.js frontend with App Routerapps/api: Fastify REST APIapps/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
- User signs in via Clerk
- Clerk middleware protects all
/dashboardroutes - On first dashboard access,
ensureOrgSynced()syncs the Clerk org and members to the database - All subsequent queries are scoped to the user’s organization