Back to projects
Active Started Sep 2025

Travel Planner

Family travel + summer camps planner. Next.js 16, Neon Postgres, Better Auth, Drizzle, Cloudflare R2. iOS companion in design.

Private Repo
Next.js 16 React 19 TypeScript Tailwind v4 Neon Postgres Drizzle Better Auth Cloudflare R2 Google Places Google Calendar

The Problem

Family travel planning is calendar Tetris. Two kids in different summer camps, each at a different venue, on overlapping schedules with different drop-off and pickup windows. Add weekly events, shared shuttle logistics, and the need for the other parent to see the same plan, and a spreadsheet falls apart by week three. Existing trip planners assume one traveler, one itinerary; family planners assume kid-free adults.

What I Built

A web app that treats family travel as a first-class problem: every event has a child, a venue, a recurrence rule, and a shared calendar destination. Built on Next.js 16 with the App Router, Neon Postgres for data, Drizzle for the ORM with an dbForUser(userId) IDOR guard so every user-data query is scoped at the wrapper layer, never raw SQL.

Summer Camps Feature

The summer-camps flow is the first complete vertical:

  • Camps CRUD with child-vs-venue disambiguation — a camp belongs to a kid and lives at a venue; both are independent records, both autocomplete from prior entries
  • Google Places API (New) v1 for venue selection — type-ahead with structured address parts, place IDs persisted for stable references
  • Google Maps Advanced Markers with per-child color coding so the map answers “where are my kids today?” at a glance
  • One-way push to a shared “Family” Google Calendar with proper RRULE strings (daily / weekly / weekdays-bitmask). Uses the calendar.app.created scope so the integration doesn’t trigger Google’s app-verification gauntlet — the calendar entries are owned by the app and stay isolated from the user’s primary calendar.

Architecture Decisions

Migrated from Supabase to Neon + Better Auth + Drizzle + R2 — Supabase’s all-in-one was great for prototyping but ran into limits on the storage and auth flexibility I needed for the iOS companion. Neon’s serverless Postgres + Vercel Marketplace integration auto-injects DATABASE_URL. Better Auth handles social-only sign-in (Google live, Apple stubbed for the iOS launch) with a Bearer plugin enabled so the iOS app can use the same backend without a second auth surface. Drizzle replaces Supabase’s auto-generated client with explicit, type-safe queries.

IDOR guard at the ORM boundarydbForUser(userId) is the only query interface application code sees. Raw db is exported only for system services (the Better Auth adapter and the Google Calendar push job). This makes the “did you remember to scope by user?” question impossible to answer wrong, because there’s no per-call decision to make.

Cloudflare R2 over Supabase Storage@aws-sdk/client-s3 with R2 endpoints. Cheaper egress, same SDK shape as the rest of the AWS ecosystem if I ever need to cross over.

proxy.ts not middleware.ts — Next 16 renamed the convention. Caught early.

What’s Next

iOS companion is planned, not built. The plan + goal are drafted; target stack is SwiftUI + SwiftData on iOS 17+, MapKit for maps (no Google Maps SDK on iOS), and EventKit deliberately not used — calendar push routes through the shared backend so the iPhone never needs Google Calendar permissions. Apple Sign-In is required by App Store Guideline 4.8 since Google sign-in is offered, so Better Auth’s Apple provider gets activated when the iOS build starts.