Back to portfolio

Cruiser Aviation - Part 1: The Decision (A Year of Debt, and Why AWS)

An European flight-school SaaS rebuilt greenfield on AWS in 11 days, demoting a year of accumulated structural debt to a requirements document and an ETL source.

Problem

A production Next.js flight-school app (Cruiser Aviation) ran for a year on Vercel and Supabase and worked well enough - pilots logged flights, invoices went out - but fast iteration left structural debt throughout. TypeScript and ESLint errors were suppressed at build time, roughly 30 debug routes were live in production, two overlapping invoice implementations coexisted alongside an invoice microservice holding state in memory, the schema had no migrations framework and 78 tables including 14 backup snapshots, and a real database password was committed to source with the app running on those credentials. The data itself was clean - every orphan check came back zero - but the structure was not. The users table carried 82 columns, around 50 of them Veriff fields present for exactly 4 users. Every invoice row had a NULL user_id. The orders table for the microservice flow had zero rows. Row-level security existed cosmetically but provided no real boundary, and auth relied on a hand-rolled HS256 shared-secret JWT system with tokens stored in localStorage. When the foundation and the walls are both the problem, incremental refactoring costs more than a rebuild with a frozen reference implementation alongside it. The decision was a 100% greenfield rewrite: the old app demoted to a requirements document, the old database to an ETL source, kept running on Vercel until DNS cutover for instant rollback.

Solution

The rebuild ran for 11 days (2026-07-25 to 2026-08-04, 194 commits) and started not with code but with documentation: a phased plan with explicit exit gates, a KEEP/DROP scope triage table, 17 per-module functional specs written from the old code, a decisions log with rationale, and a production data audit before any schema design. AWS was chosen over staying on Vercel/Supabase because it placed the database, compute, files, email, auth, secrets, queues, and Bedrock under one IAM roof with no API keys in application code, provided AWS End User Messaging Social for WhatsApp with no Meta tokens in the codebase, and allowed Aurora Serverless v2 to scale staging to zero ACU when idle - the full two-environment estate targeting $250 - 450/month. The schema collapsed from 78 tables to 31 plus 5 reference tables using enforced conventions: snake_case, UUID PKs, declared foreign keys, no RLS (authz as app-layer role checks), money as numeric with an explicit currency column, and S3 keys instead of URLs. ETL was roughly 40 idempotent Node scripts - dry-run by default, rerunnable at every rehearsal - hitting Aurora over the RDS Data API with no bastion. The headline ETL find was a ledger under-count: around 65 users appeared falsely overdrawn because the old importer had missed roughly 1,199 bought hours. A reconciliation gate checked two per-user properties using the production FIFO engine imported directly, so the check could not drift from the app. Auth migrated 377 users from bcrypt hashes under a hand-rolled JWT system to Cognito with zero password resets, using a user-migration Lambda that bcrypt-verified credentials on first login and silently minted confirmed Cognito users. A pre-token Lambda injected roles into the ID token while keeping authorization entirely in Postgres. The app issues no tokens and holds no signing secrets; sessions are Cognito RS256 JWTs in httpOnly cookies verified against Cognito's JWKS.

Tech Stack

Next.jsTypeScriptAWS CDKAurora Serverless v2Amazon CognitoAWS FargateAmazon CloudFrontAWS End User Messaging SocialAmazon BedrockAmazon DynamoDBAmazon SNSAmazon SESRDS Data APIDrizzle ORMSupabase PostgresVercelNode.jsjosebcryptGitHub OIDC

Ask about Julian Walder

Grounded in his real work

Hi! I'm Julian Walder's assistant. Ask me anything about his work, projects, or background in AI.