Tourismo

Tourismo

Tourismo is a product I'm building for people who actually enjoy driving — not commuting. The Sunday-morning, take-the-long-way, ring-the-mate-with-the-good-car kind of driving. It's also the kind of build I find clarifying as an architect: a small surface area, a handful of genuinely hard constraints — privacy, offline, solo-operable cost — and nowhere to hide a sloppy decision behind a big team.

The problem

If you're into cars, your best roads and your best days end up scattered. A route someone screenshotted into a WhatsApp group eighteen months ago. A Cars & Coffee that lives on a Facebook event you can never find again. Photos of a drive buried in a camera roll with ten thousand other things. There's no system of record for any of it. The fitness apps are the obvious comparison, but they're built around pace, streaks and leaderboards, which is exactly the wrong model. Nobody enjoying a B-road wants to be told they were four seconds slower than last week. The real problem isn't a missing feature — it's that the domain (drives, cars, meets, the people you share them with) has never been modelled properly, so it lives in a dozen tools that each hold a fragment.

What it does

Three capabilities, each shaped by a constraint rather than a feature list. You log a drive — with GPS, without it, or imported from a track-day logger — which forces the data model to treat a track as optional and a drive as the real entity. You organise a meet — a cruise, a coffee run, a track day — as one link with RSVPs, a route and a calendar time, so the unit of sharing is a URL, not an app install. And you keep a garage, where every car carries its own visibility: your daily can be public, the rare project car private or followers-only. Visibility being a property of the entity, not a global account toggle, is the decision the rest of the access model hangs off.

That last point matters more than it sounds, because it's a threat model, not a preference. Car people have real stakes — rare cars get stolen, and a shared drive shouldn't draw a stranger a map to your front door. So Privacy Zones aren't a display filter that hides the start of a trip; they trim the track before it's ever stored. You draw a zone around home, and the data leaves your device already cropped. Designing for "the server never receives the sensitive coordinates" instead of "the server hides them" is the whole game, and it dictates where the trimming has to run.

The architecture

It's a React/TypeScript PWA on Vite, sitting on a fully serverless AWS backend — CloudFront → S3 for the static app, Cognito issuing the JWT that authorizes API Gateway → Lambda, and DynamoDB behind it. No EC2, no Amplify. That shape isn't fashion; it's the only way one person runs production credibly. There's no server to patch, no connection pool to exhaust, no instance to right-size, and the bill is genuinely zero when nobody's driving — the operational surface area is small enough that I can hold the whole system in my head.

The data layer is the decision I'd defend hardest. DynamoDB is PAY_PER_REQUEST with a table per entity rather than the single-table orthodoxy — eleven-plus of them — because for a solo build, a schema I can still read at 11pm is worth more than the RCUs single-table saves. The access patterns that matter are designed straight into the keys: "my drives, newest first" is one query against a byUser_startedAt GSI, partitioned on userId and sorted descending on startedAt, no scan and no filter. I'm equally clear on where the model bends: the social feed wants a join DynamoDB can't do, so the home feed becomes a fan-out in the Lambda — a trade-off I made deliberately and wrote up honestly in Why I chose DynamoDB for Tourismo.

Privacy Zones live where the threat model says they must — on the device. A trimWithPrivacyZones function does a haversine cut plus a 200 m approach buffer (the lane up to your door is nearly as identifying as the door itself), and the same function runs on the server's retroactive redact endpoint, so client trim and server trim can never drift into redacting differently. And the whole stack is Terraform, treated as a product in its own right: single-purpose modules (auth, data, storage, api, observability), deployed by OIDC-authenticated CI that holds no AWS keys, with plan-on-PR and a gated apply against S3 + DynamoDB-locked remote state. The infrastructure gets the same review gate as the application code, because future-me is the only person who can fix it. I wrote up that structure in Terraform as product and the on-device trimming in Building privacy-first location tracking.

The detail I enjoyed most sits on the seam between design and engineering: the Discover map on the landing page. The hero route isn't decorative — it's the real Sally Gap polyline, decoded and projected onto an SVG, so the artwork is a road you can genuinely open in the app rather than a stock illustration.

Tourismo architecture diagram: on the device, a React/TypeScript PWA captures a drive and trims it with Privacy Zones — a haversine cut plus a 200-metre approach buffer, the same trim function the server's redact endpoint runs — so only cropped data crosses HTTPS to AWS. CloudFront and S3 deliver the static PWA, Cognito issues the JWT that authorizes API Gateway, a serverless Lambda API reads and writes DynamoDB (pay-per-request, per-entity tables with purpose-built GSIs like byUser_startedAt) and S3, and every resource is declared in Terraform modules deployed by OIDC-authenticated CI with S3 and DynamoDB-locked remote state.
Where the device ends and the cloud begins. Privacy Zones (highlighted) trim the track on-device — haversine plus a 200 m buffer, the same code the server redact path runs — so data crosses the boundary already cropped. Behind the API Gateway authorizer, DynamoDB's keys are shaped to the access patterns, and the whole AWS side is declared in Terraform modules.

Where it's at

Closed beta, and the constraints around it are deliberate. Invite-managed keeps the user set small enough to change the data model without a painful migration; Ireland-first keeps the geography I have to reason about — roads, meets, mapping — to one I actually know. It's built around the roads I drive: Sally Gap, the Healy Pass, the Mournes. Scaling the audience is the easy part later; getting the entities, the privacy model and the cost shape right while it's small is the part that's hard to redo. If that sounds like your kind of weekend, it lives at tourismoclub.com.