
DriveFlow: Multi-tenant SaaS on Supabase
My first SaaS and first real team project. How we structured a multi-tenant driving school platform from day one — RLS, PWA, Stripe Connect, and what working with more than two people actually teaches you.
Driving schools in German-speaking Switzerland still run on paper. Lesson notes go in a logbook, scheduling happens over WhatsApp, invoices get sent as PDFs. Most instructors spend real hours every week on admin that could be automated. That gap is what DriveFlow addresses.
DriveFlow is a mobile-first SaaS PWA for driving schools: separate dashboards for students, instructors, and school owners — scheduling, lesson documentation, competency tracking, and payments — in one place, installable from the browser, no app store required.
My first SaaS. My first real team.
Until DriveFlow, every project I shipped was solo or two people at most. Building it under Aeternum Software changed that. Working with a real team introduced overhead I hadn't dealt with before: branch strategies that don't break each other's work, shared conventions for naming, file structure, data flow. Things that don't matter when you're alone become load-bearing when three people touch the same codebase.
What surprised me was how much architecture decisions matter more, not less, with more people. A poorly structured data layer is annoying when you're solo. With a team, it's a source of bugs and arguments.
Why multi-tenancy has to come first
DriveFlow is multi-tenant: each driving school is a completely isolated tenant. Students, lessons, and billing data never cross school boundaries.
The temptation with SaaS is to treat tenancy as an afterthought — ship the product, add a school_id column everywhere later. That path leads to bugs that are hard to test and dangerous to ship. We leaned into Supabase's Row Level Security from day one instead.
Every table that holds tenant data has RLS policies that check the user's school membership before allowing any read or write. The database enforces isolation — not the application layer. A simplified version:
CREATE POLICY "Students see their own school's lessons"
ON lessons FOR SELECT
USING (
school_id = (
SELECT school_id FROM school_members
WHERE user_id = auth.uid()
LIMIT 1
)
);No matter what the frontend sends, the database only returns rows that belong to the user's school. Tenant leakage at the query layer becomes structurally impossible.
Three roles, one data model
DriveFlow has three user types: students, instructors, and school owners. Owners are a superset of instructors — same dashboard, plus billing access and team management.
We store this in a single school_members table with a role column. RLS policies reference the role for any write that requires elevated permissions. One membership record per user per school. No three separate auth flows, no three separate user tables. The data model stayed lean.
PWA over native
Building native apps for iOS and Android meant two codebases, app store reviews, and a slow feedback loop. Driving instructors need the app between lessons — they won't wait for a two-week review cycle when something breaks.
A PWA built with Next.js and Serwist gives us a single codebase that installs on any home screen, works offline (critical for instructors in areas with poor signal), and ships updates instantly. The offline layer caches the app shell so the core UI stays functional without internet — no spinner, no crash.
Stripe Connect for service businesses
Most SaaS billing is platform → customer. DriveFlow inverts it: students pay lesson invoices directly to the school, and the platform takes a fee. Stripe Connect handles this — schools onboard as Stripe accounts, students pay to the school's account, the platform collects its cut automatically.
The billing logic is more complex than a standard subscription, but it's the right model for small service businesses. Schools don't want to reconcile cash or bank transfers from twenty students. A card payment with automatic payout is something they'll actually pay a platform fee for.
Tech stack
- Next.js 16 + React 19 — App Router, TypeScript strict mode throughout.
- Tailwind CSS v4 + shadcn/ui — dark mode, mobile-first.
- Supabase — PostgreSQL, Auth, RLS. All data access lives in
lib/queries/— swapping the backend later only touches one layer. - TanStack Query + Zustand — server state and UI state separated.
- Stripe Connect + Stripe Billing — per-seat subscriptions and student invoice payments.
- Serwist — service worker, offline caching, PWA manifest.
- Playwright + Vitest — E2E and unit tests. Deployed on Vercel.
Where it is now
MVP feature-complete: auth, onboarding, student and instructor dashboards, lesson management, competency tracking, signed JWT invite links, Stripe billing, and school discovery. The UI is in German — that's the primary market — with the architecture ready for i18n. First schools onboarding soon.


