Route setting in a climbing gym seems fairly straightforward when you're looking at it from the outside. Old routes come down, new ones go up, somebody grades them and the wall opens again.
There is quite a bit more going on behind the scenes.
Routes get old. Sectors need regular resets. Setters have to know what they're supposed to build. Someone needs to decide how many new routes are needed and which grades are missing. Then all of that has to be planned around people, dates and the fact that the gym still needs to operate.
You can manage a lot of this with spreadsheets.
Up to a point.
That's basically where RouteFlow started.
What I'm building
RouteFlow is a SaaS application for managing route setting in climbing gyms.
I'm deliberately focusing on the operational side of the gym rather than building another app for climbers to log their ascents.
At the centre of RouteFlow is a fairly simple relationship:
Sector → Setting Session → Setter Tasks → Routes
A manager or head setter plans work for a sector, decides how many routes should be created and assigns individual tasks to setters.
The setters work through those tasks, the routes get tested, and finished work can eventually become an actual Route in the gym's database.
That last part turned out to be more interesting than I initially expected.
Django on the backend, React on the frontend
The backend is built with Python, Django and Django REST Framework, with PostgreSQL as the database. The application runs in Docker.
The frontend is React with TypeScript and talks to Django through the REST API.
There isn't anything particularly exotic about that stack, and that's intentional.
I'm building RouteFlow as something that should eventually be used by actual climbing gyms. I would rather have boring technology with understandable behaviour than introduce another piece of infrastructure just because I can.
A gym has to actually own its data
Once RouteFlow started moving beyond basic CRUD, one of the first things that needed sorting out properly was data ownership.
The natural boundary is the gym.
A sector belongs to a gym. Routes inside that sector belong to the same gym. Setting sessions and the tasks created within them need to respect that boundary as well.
And simply hiding Gym B's data from somebody logged into Gym A wasn't enough.
The API itself has to enforce it.
So the Django querysets are scoped to the authenticated user's gym, and I've been testing the API by deliberately trying to retrieve and modify resources belonging to another gym.
Those requests return 404.
The same rule applies when creating relationships between objects. For example, a setter belonging to one gym shouldn't be assignable to a setting session in another one.
This is the kind of work that isn't particularly exciting in a screenshot, but it's much closer to the problems I expect a real SaaS application to have.
The setting workflow
A Setting Session describes a planned reset or setting operation for a sector.
It contains things such as the date, lead setter and target number of routes. From there, individual Setter Tasks can be created and assigned.
A task currently moves through:
Todo → In Progress → Testing → Done
It can also contain a target grade, hold colour, setter and due date.

Originally, getting a task to Done looked like the natural end of that workflow.
It isn't.
A finished task means the setter has finished the work. It doesn't necessarily mean RouteFlow has a new Route in its database.
So publishing became a separate backend operation.
Once a completed task is published, Django creates the Route and links it back to the original Setter Task. The operation is atomic, so if part of that process fails, it doesn't leave half-created data behind.
It also means the same task can't accidentally produce two routes.
That's a relatively small feature from the user's point of view — essentially an action in the interface — but it forced me to think much more carefully about where one part of the workflow actually ends and another begins.
Then I built the dashboard
The most recent part of RouteFlow is the operational dashboard.
By this stage the application already knew about setting sessions, tasks and published routes. The problem was that knowing something and making that information useful are two different things.
I wanted the dashboard to answer fairly mundane but important questions:
What's being set right now?
How far along is it?
What's coming next?
Is anything late?
Are there enough tasks assigned to actually hit the planned number of routes?
So an active session now shows its sector, lead setter, planned date, target route count and the current state of its tasks.
Task progress is split between Todo, In Progress, Testing and Done.
The dashboard also compares the target number of routes with the number actually published.
That distinction caused one small change in how I thought about completion.
At first it would have been easy to calculate session progress from tasks marked Done. But a done task isn't necessarily a published route.
If a session is supposed to produce 10 routes and six tasks are done but only four routes have actually been published, calling the session 60% complete would be misleading.
So RouteFlow considers it 40% complete.
It sounds like a minor detail, but those are exactly the details that start appearing once separate features have to work together as one system.
Things that need attention
The dashboard also has an Attention Required section.
Currently it catches things such as overdue tasks, overdue setting sessions and sessions where the number of assigned tasks is lower than the target number of routes.
I actually considered adding an alert for sessions without a lead setter while building this.
Then I realised the application already requires a lead setter when creating a session.
There was no point adding dashboard logic for a state the system doesn't allow to exist.
Removing that check was probably more useful than adding another dashboard card.
Where it is now
RouteFlow isn't finished, and I don't want to present it as if it is.
What works now is the core operational chain:
Setting Session → Setter Tasks → Task Workflow → Route Publishing → Operational Dashboard
The next milestone is mostly cleanup.
There is still some demo and fallback data left from earlier stages of frontend development. I'm removing that and checking the application screen by screen to make sure the API is the actual source of truth everywhere.
After that I want to run the whole thing the way a new gym would: start with an empty account, create the gym structure, add setters and routes, plan a session, assign the work, publish the resulting routes and see whether anything requires developer intervention along the way.
If it does, v0.1 isn't ready yet.
Once that works, the useful part starts: putting RouteFlow in front of people who actually run climbing gyms and seeing which assumptions were right and which ones weren't.
For me, RouteFlow is also a good example of the kind of development work I want to do more of: Django, REST APIs and applications where the interesting problems aren't just about displaying data, but about defining what should happen to it.
The code
RouteFlow is still under active development, but if you'd like to take a look at the project itself, the source code is available on GitHub:
I'll be sharing more of the technical decisions, mistakes and lessons from building RouteFlow as the project moves toward its first usable release.

Top comments (0)