Campus2Care
Volunteer onboarding, messaging and hour tracking. One shared backend serving both a web app and a native app, without a second API tier existing at all.
- Architecture
- Turborepo monorepo
- Clients
- Web (PWA) + native
- Authorization
- Postgres RLS
- Files to add native
- One
Problem
Campus2Care needed volunteer onboarding, messaging and hour tracking on both the web and a native phone app. The default path, a Next.js API tier in front of the database with a second client written against it, duplicates auth, pagination and upload logic across two codebases and doubles the surface where they can drift apart.
Approach
Supabase is the backend; Next.js is a client of it, not a gateway to it. Both the web app and the React Native app talk to Supabase directly. Route Handlers exist only where a server secret is genuinely unavoidable: CSV export, push fan-out, webhooks.
That single decision is what keeps a second API tier from existing at all, and with it a second set of auth, pagination and upload code.
Architecture decisions
Row Level Security is the authorization layer. Not a convention enforced in application code that a new endpoint can forget, but a property of the database that every client inherits automatically. Privileged multi-step operations are SECURITY DEFINER Postgres functions that the web and native clients call identically.
Because RLS is the authorization layer, the pgTAP suite is the most important test suite in the repo. A red policy test is not a failing unit test, it is a data-exposure bug, and it is treated as one.
packages/api never imports a platform. No next/headers, no AsyncStorage. Every function takes an injected Db client that the platform constructs and passes in. Packages ship TypeScript source rather than build output, so there is no build step and no stale dist/. Next compiles them through transpilePackages, and Metro reads TypeScript natively.
The payoff is measurable: adding the entire native app required exactly one new file, lib/supabase.ts.
Layout
apps/web is Next.js 16, the volunteer PWA and the /admin dashboard. apps/mobile is Expo SDK 57.
packages/core holds domain types, zod schemas, and pure logic like hour maths and term bucketing, with no I/O. packages/api is Supabase data access. packages/db holds declarative schemas, migrations, RLS policies, pgTAP and generated types. packages/tokens emits design tokens as CSS for web and TypeScript for mobile.
Built with
- Turborepo
- · Next.js 16
- · Expo SDK 57
- · Supabase
- · Postgres RLS
- · pgTAP