Why is routing so complex?

If you write JavaScript code, it’s very likely you’ve already encountered some of the existing routing solutions. On the client side, there is a lot of them, depending on which framework you use. On nodejs, the most popular one is express. I’ll be focusing on client side routing, but the same ideas apply to server side routing.

Angular, Ember and React have different solutions for routing. But they have one thing in common: they are complex and take time to learn.

Let’s look at Ember for example. Here’s a typical router file:

Router.map(function() {
this.route('about', { path: '/about' });
this.route('posts', function() {
this.route('new');
this.route('favorites');
});
});

The first questions that come to mind are why some of the routes have apath while others don’t? Where are the handlers for these routes? We are referring to this inside the callback functions, do we need to bind them?

Then we need to learn how to define route handlers. And learn how Ember magically matches a route with its handler based on the name. And learn how to use route hooks to fetch data. And the list goes on.

React Router, on the other hand, does less magic and keeps things explicit. But it still suffers from the same API bloat and learning curve problems.

<Router>
<Route path="/" component={App}>
<IndexRoute component={AppIndex} />
<Route path="about" component={About} />

<Route path="posts" component={Posts}>
<Route path="new" component={NewPost} />
<Route path="favorite" component={FavoritePosts} />
</Route>
</Route>
</Router>

Similar questions: what other props does <Route> support? What’s an index route, and how is App different from AppIndex? Etc..


Let’s take a step back

What’s the main responsibility of a router? In the client side world, the router has the single responsibility of mapping url paths to UI content. It’s as simple as that. Similarly, on server side, the router maps HTTP requests to HTTP responses.

Since routing is just a mapping, why not use plain ol’ JavaScript objects?

// routes.js
{
'about': <About />,
'posts': {
'new': <NewPost />,
'favorites': <FavoritePosts />,
}
}

Well, that look promising. But wait. Those are all static components. What if I need dynamic routing? What if I want to render different things based on some variable? Fortunately, functions are first-class citizens in JS:

// routes.js
{
'about': <About />,
'posts': {
'favorites': () => isLoggedIn() ? <Favorites /> : <Login />,
'{id}': (route) => <Post id={route.params.id} />,
}
}

Alright, but what if I need to fetch some data before I render the component? What if I need to lazy-load the component? Promises come to the rescue!

// routes.js
{
'about': () => lazyLoad('About').then(About => <About />),
'posts': {
'favorites':
() => fetchFavs().then(favs => <Favorites favs={favs} />),
}
}

Now of course putting all these routes in a single file would make a big mess. But because the routes are simple JS objects and functions, they can easily be modularized and split into multiple files:

// posts/routes.js
export default {
'favorites': ...,
'{id}': ...,
};
// profile/routes.js
export default {
'view': <ProfileView />,
'settings': <ProfileSettings />,
};
// routes.js
import About from './about';
import PostsRoutes from './posts/routes';
import ProfileRoutes from './profile/routes';
export default {
'about': <About />,
'posts': PostsRoutes,
'profile': ProfileRoutes,
};

Thanks for reading! I’d love to hear your thoughts :)