Same App, Ten Years Apart

Same App, Ten Years Apart

In 2017 I wrote a post called Modernizing Pedalwrencher: Whatever That Means, about dockerizing a two-year-old Flask app and moving it from Heroku to AWS ECS. Reading it now is like finding a letter from a previous self who thought he was done modernizing.

This year I rebuilt Pedal Wrencher from scratch, and the interesting part is what a controlled experiment it turned out to be. The product is identical: miles come in from Strava, “replace your chain” goes out. Same feature, same user, same me. The only variable that changed is the decade. That makes the diff between the two codebases a pretty clean measurement of what ten years of tooling actually bought.

The diff

Flask to FastAPI. The 2015 app was a single Python file, under 300 lines, two models, five pages. The 2026 app is FastAPI with server-rendered Jinja templates. Async matters for an app whose whole job is waiting on someone else’s API, and typed request handling catches the dumb bugs at the door. Honestly though, of everything on this list, the web framework matters least. Flask would have been fine.

Twilio SMS to email. Notifications moved from text messages to transactional email. Partly cost, partly the deliverability arms race around SMS, mostly that the 2015 novelty of a robot texting you has curdled into menace. Email survives because an inbox is where reminders are supposed to live.

Permanent tokens to rotating ones. In 2015, Strava handed you an access token that worked forever. The modern OAuth flow issues six-hour access tokens with rotating refresh tokens, and Strava no longer shares the athlete’s email address at all, so the app has to ask for it at registration. Both changes are strictly better for users and both are more code for me. That’s the correct direction for that tradeoff to point.

Heroku, then ECS, then Render with Terraform. The 2017 post documents real weeks spent on Docker, ECS task definitions, RDS, Route 53, and CloudWatch. The 2026 equivalent is a couple hundred lines of Terraform targeting Render: web service, Postgres, scheduled worker, custom domain, done. Everything the 2017 migration bought me is now the baseline offering of any platform-as-a-service, provisioned from a file in the repo instead of a console I clicked through and forgot.

Cron on a box to a scheduled worker. The offline job that polls Strava survives in spirit, but it’s now a proper scheduled job with monitoring, a heartbeat ping, and a rate-limit budget, instead of a cron entry I checked on whenever something seemed quiet.

What actually improved

Tallying it up, the wins cluster in two places: operations and security. Deployment went from a project to a property of the repo. Token handling went from “store forever and hope” to short-lived credentials encrypted at rest. Observability went from ssh-and-grep to dashboards I didn’t build.

The product improved zero percent. A 2015 user teleported to 2026 would notice the emails, the shoe tracking, and nothing else. Every hour of the rewrite went into the parts users can’t see, and the parts users can see were already done eleven years ago, in 300 lines of Flask.

The uncomfortable observation

The 2015 version took a weekend. The 2017 modernization took weeks. The 2026 rewrite, of the entire application, took days. Some of that is AI-assisted coding, some is genuinely better tools, but a lot of it is that the industry spent the decade paving exactly the roads this kind of app drives on: managed Postgres, infrastructure as code, transactional email APIs, OAuth libraries that handle rotation.

Whatever “modernizing” means, and 2017 me never did pin it down, I no longer think it’s something you do to an app. It’s something that happens to the ground under the app, whether you rebuild or you don’t. The 2026 version will look just as dated by 2036. The product, I suspect, still won’t need to change.