Flying clubs and FBOs (fixed-base operators) still run on dated software like MyFBO. Azimuth is a modern replacement covering the full operational core: members and their pilot credentials, the aircraft fleet and its maintenance, scheduling and dispatch, flight logging, and member billing — the loop where a reservation becomes a checked-out aircraft, becomes a logged flight, becomes a posted charge on a member's ledger. It's built multi-tenant from day one (every domain table is org-scoped, so adding a second club needs no schema change) and access is capability-based, not role-based. I designed and built the whole thing solo: schema, server actions, auth, RBAC, and UI.
What it's built with.
Application
- Next.js 16 (App Router)
- React 19 Server Actions
- TypeScript
- Tailwind CSS v4
Data & Auth
- PostgreSQL
- Drizzle ORM (migrations)
- Auth.js v5 (JWT)
- Multi-Tenant org scoping
Domain & Ops
- Capability-Based RBAC
- Scheduling & Conflict Detection
- Maintenance / Currency Tracking
- Vercel Cron Automation
How it works.
The operational loop
Running a flying club is a chain of dependent events, and the value of the software is in keeping that chain consistent. A member books a reservation; a dispatcher checks the aircraft out to them; the flight is flown and logged, which advances the Hobbs and Tach meters; logging the flight auto-posts the rental and instruction charges to the member's ledger and closes the reservation. Azimuth models that whole loop as connected modules rather than disconnected CRUD screens.
The maintenance side runs in parallel: each aircraft tracks inspection due-points (annual, 100-hour, etc.) against its current Hobbs/Tach, and an aircraft that goes out of currency or picks up a grounding squawk is automatically removed from the dispatchable fleet — so the scheduling module can't book a plane that shouldn't fly.
Multi-tenant and capability-based from day one
Even though it runs a single club today, Azimuth is built multi-tenant from the schema up. A user is a global identity (one login), and a membership joins that user to an organization with a role; all club data references a membership, not a user, and every domain table carries an org_id that scopes every query. Adding a second club later is an org switcher that re-issues the session token — no schema migration.
Authorization is capability-based, not role-based. Roles (admin, dispatcher, instructor, member, student, mechanic) are just bundles of fine-grained capabilities, and every server action and UI affordance checks the capability — dispatch:write, maintenance:manage, schedule:manage_others — rather than testing the role directly. That keeps permission logic in one place and makes it safe to add a new role or re-scope an existing one without hunting through the codebase.
Modern stack, real automation
Azimuth is built on Next.js 16 with React 19 Server Components and Server Actions as the primary data path, Auth.js v5 for credentials auth with edge-safe middleware, and a Drizzle/Postgres backend with a versioned migration history (44 and counting). It deploys to Vercel against hosted Postgres (Supabase / Neon), with TLS auto-enabled.
Beyond request/response, it runs real operational automation: a Vercel Cron job hits a secured endpoint every 15 minutes to send time-based reminders, and it fails closed without its bearer secret. Pilot credential documents — medicals, certificates, endorsements — live in a private storage bucket and are served only through short-lived signed URLs, while low-sensitivity profile avatars use a public bucket with graceful fallback to initials when storage isn't configured.
The things I'm proudest of.
- ▹Built the full operational loop across six modules — Members & roles, Fleet & maintenance, Scheduling & dispatch, Flight logging, and Billing — where logging a flight advances Hobbs/Tach meters, auto-posts rental + instruction charges to the member's ledger, and closes the reservation in one transaction.
- ▹Designed a ~60-table PostgreSQL schema with Drizzle ORM (44 migrations), multi-tenant from day one: a global users identity, memberships joining a user to an organization with a role, and an org_id on every domain table so all queries are org-scoped — a second club requires no schema change.
- ▹Implemented capability-based access control (lib/rbac.ts): six roles (admin, dispatcher, instructor, member, student, mechanic) map to fine-grained capabilities (e.g. dispatch:write, maintenance:manage, schedule:manage_others), and every server action + UI surface checks capabilities, not raw roles.
- ▹Built on Next.js 16 (App Router, React 19 Server Components + Server Actions) with Auth.js v5 (credentials, JWT sessions, edge-safe middleware), reservation conflict detection, inspection-due tracking (annual / 100-hr) with aircraft grounding, and a per-member A/R ledger.
- ▹Wired operational automation: a Vercel Cron job hits a secured endpoint every 15 minutes to run time-based reminders (upcoming reservations, close-your-flight-plan nudges), failing closed without its bearer secret; credential scans (medicals, certificates) are served via short-lived signed URLs from a private Supabase Storage bucket.
What it looks like.






