Routing Design Patterns

Will Farley
Nov 4 · 4 min read

Routes are a powerful state management option that I find is often overlooked in application designs. Maybe this is because developers are thinking something like “State management task? Redux!” or because the browser History specification came sometime after SPAs started gaining traction. Regardless, since the History API has broad support across browsers nowadays, it’s something we should be considering when architecting applications. Now I should clarify: I’m not suggestion routing is an alternative to other state management, I’m suggesting it’s something that you should consider using alongside another, primary, state management tool. Here are some of the benefits of using routes for state management:

  • Better platform (browser) integration: Our application state is connected to the browser, so our application experience can benefit from features provided by the browser UI. These include: undo/redo actions (back/forward navigation), saving snapshots of an app state (bookmarks), sharing app states (sharing a link), loading an app from a previously saved state (opening a link), and breadcrumbs (browser URL bar).
  • Framework agnostic: Routing is managed outside of the DOM, so it doesn’t matter what framework or library you are using.
  • Decoupled: Because routing isn’t stored in the DOM, it’s also decoupled from your application implementation details.

Terminology

Before we proceed further, let’s define a couple of terms I use throughout this post that might be ambiguous.

  • Route: Specifically, this is the path portion of a URL. Everything that comes after the protocol, domain name or IP address, and the domain extension but before query parameters and fragment. In this example, everything that is in bold would be the route: https://www.cats.com/cats/poppy/hobbies/napping#list?filter=active
  • Type: A type of entity. Articles, cats, places, items, and movies would be types of entities.
  • Entity: An object, model, thing displayed in the browser. An article, cat, place, item, and a movie would all be entities.
  • Action: Something that a user does against an entity. An action describes what is being done and realized when the application state is changed.

TIA Route Design Pattern

TIA Routing Design Pattern

Routes design revolves around types, entities, and actions. Routing should be used to answer these state management related questions:

  • What type of entity/entities?
  • What entity?
  • What action am I taking on this entity/entities?

Keeping these questions in mind we can also answer the question: should I use my application’s primary state management system or routing to handle this piece of state? If the state management implementation is solving any of those three questions, then use routing, otherwise consider using your primary state management tool or query parameters. Keep in mind that it’s challenging to maintain multiple sources of truth, so don’t duplicate your router state inside your primary state management tool.

Routes should follow this pattern:

/<Type>/<Identifier?>/<Action?>

Where Type, Identifier, and Action (TIA) refer to an entity. For example:

/dogs/snowflake/walk
  • “dogs” refers to the type of entity and should be in plural form.
  • “snowflake” is a unique identifier for an entity, if possible, in a human-readable form.
  • “walk” is the action taken on the entity.

A more realistic example would be:

/posts/1/edit

You get the idea.

When there is no <Identifier> in the route, you should be displaying an array of entities. <Action> then refers to an action that isn’t specific to one entity.

For example:

/posts

Displays all posts and

/posts/create

Shows the form to add a new post to the array of all posts.

Nested Routes

Following the mentioned TIA pattern described previously, we can nest entities that are related:

/<Type>/<Identifier?>/<Type>/<Identifier?>/<Action?>

It’s essential to separate entity <Identifier>s with a <Type> in case there are multiple relationships you want to support.

/dogs/snowflake/places/park/walk

When there is no second <Identifier>, you should be displaying all the related entities or taking an <Action> that isn’t specific to a single related entity (same as in the non-nested example):

/posts/1/comments/create

<Action> is placed at the end of the route. If an action is being taken, it should be against the related entity, not the parent entity. You should only need to define one action per route, so doing this should be avoidable:

/<Type>/<Identifier?>/<Action?>/<Type>/<Identifier?>/<Action?>

Dynamic Routing

Dynamic Routing is a concept coined in the React Router Philosophy: “Routing that takes place as your app is rendering, not in a configuration or convention outside of a running app.” By following the patterns we reviewed earlier, we design our routes in a way so that they can be composed dynamically. We can declare routes in the component rather than at some top-level static routing configuration. For example we can create a <Dog> component that declares a route “dogs/:dogName” and a <PlaceList> component with a route “places”. We can easily drop the <PlaceList> inside the <Dog> component to compose them together. Dynamic Routing is a pattern that deserves more detail but is out of the scope of this article. I hope to follow up with more details at a later time. I attempt to demo some of these concepts in the react-router codepen. Explanations are inline; you can click around and see the documentation and look through the code.

Slashes

When declaring composable routes on components, keep in mind that you will need to enforce a usage pattern around slashes “/”. For example, you might want to enforce that all routes need to have a trailing slash or that all routes must have a leading slash and no trailing slash. Either way, you will need this defined to allow for route composition without having to check every time if the declared route does or doesn’t have a slash before composing.

Conclusion

We’ve covered the TIA (/<Type>/<Identifier?>/<Action?>) design pattern, when to use routes, when we should use query parameters or a primary state management tool, how to nest routes, and a high-level overview of Dynamic (Compositional) Routing. The concepts and patterns described in this article should help you come up with a scalable routing design that works with both static and dynamic routing.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade