Chapter 4: Routing, Part 1

Routing, Part 1

Later on, we’re going to make it possible for users to organize their to-do list items into separate lists (categories, essentially), and each of these lists is going to have its own page with the following URL structure:

http://localhost:3000/list/Nnm8KL9HXHPCX58rs

(The random jumble of numbers and letters is the unique ID associated with that particular list.)

But before we can learn how to create different lists with different pages, we need to learn the basics of how to create multi-page applications with Meteor. It seems like the sort of thing that should be obvious, but it’s not.

If we were creating a web application with PHP, for instance, then we could create a handful of separate PHP files to represent each page:

  • index.php
  • about.php
  • contact.php

But when working with Meteor – and most modern frameworks, for that matter – we don’t take this approach. Instead, we use what’s known as routing.

Routing can be a jarring concept for beginners to learn, but by the end of this chapter, you’ll have a clear understanding of:

  • What routing is.
  • Why we use routing.
  • How to create routes.

From there, we’ll be able to build some of the project’s more advanced and interesting features.

What are Routes?

At their most basic, routes are used to associate a URL path with a template. We could, for instance, create an “aboutPage” template:

<template name="aboutPage">
    <h1>About</h1>
</template>

…and associate it with the following URL path:

http://localhost:3000/about

So if a user visited the above URL path, the content within the “aboutPage” template would be shown to them.

But routes can do a lot more than associate a URL path with a template. They can also hold logic, for instance. This allows us to only show the content of a certain route to logged-in users. Or we can attach a data source to routes, which means we don’t have to create a separate helper function for it.

As a beginner, the scope of what routes allow us to achieve can be somewhat difficult to fathom, but ultimately:

  • Routes allow us to write less code when building multi-page applications.
  • Routes make the code-base of larger applications easier to manage.
  • Routes solve a number of boring (and previously difficult) problems.

Early on, routes might just feel like a different way of working, but the more code you write, the more you’ll see how they improve your life as a developer.

Iron Router

To create and manage routes within our project, the best option is to use a third-party package that adds routing capabilities to Meteor, and at the moment, there are two main packages to choose between:

Iron Router is the most popular option and it’s what we’ll be using throughout this book, but Flow Router does have the edge in terms of simplicity, so you may like to play around with it yourself at a later time.

(Most of what we learn about Iron Router will be applicable to Flow Router. It’s just that the syntax will be a little different.)

In the previous book, we talked about how to install official packages, like the “accounts-password” package, but now how to install third-party packages. The process is quite similar though.

To install the Iron Router package, for instance, we just have to enter the following command into the command line:

meteor add iron:router

When compared to installing official packages, the only difference is the colon that’s placed between the words “iron” and “router”, and the colon is there because the “iron” part is actually a username while the “router” part is the name of the package itself. All third-party packages are named in this way to avoid name-based conflicts. If, for example, someone created their own “router” package, it would be differentiated from the “iron:router” package since a different username would appear before the colon.

Tap the “Return” key to install the package and notice that, back in the browser, the following message will appear within the interface:

This message is explaining how we can create a route for the application’s home page, but since we’ll do that ourselves in a moment, we can ignore what it says. It does at least confirm that Iron Router is working though.

Basic Routes

Later on, we’re going to add a user accounts system to our project, and we’re going to need a page for signing up and a page for logging in. To prepare for this, we’ll setup a pair of “Register” and “Login” pages using routes.

To begin, let’s focus on the “Register” page.

Inside the HTML file, create the following “register” template:

<template name="register">
    <h2>Register</h2>
</template>

Then, inside the JavaScript file (but outside the isClient and isServer conditionals), write the following statement:

Router.route('/register');

Here, we can see the simplest possible syntax for creating a route, and there’s a couple of things worth mentioning:

First, we’re using this Router.route function to define the route. This function is provided to us by Iron Router, and it’s what we’ll use to define each of the routes that we want to create.

Second, we’re passing a path into this function. In this case, that’s the “/register” path, which will result in the following URL being created:

http://localhost:3000/register

Save the project, switch back to the browser, and visit the above URL to see that when we visit the “/register” path, the “register” template is shown.

You can repeat this process by creating a “login” template:

<template name="login">
    <h2>Login</h2>
</template>

…and a route for the “/login” path:

Router.route('/login');

But how do these routes “know” which templates to display?

Iron Router assumes that, if we create a “/login” route, then we probably want that route associated with a template named “login”. As such, we don’t have to manually define the association.

We can define a manual association between a route and a template – and we’ll talk about that in the next section – but as much as possible, Iron Router tries to intelligently guess what we want to do, which means we have fewer things to think about when writing code.

Notice, however, that when we visit either of our routes, the “todos” template appears above the templates associated with each route:

This is because we have the following code inside the HTML file:

<head>
    <title>Todos</title>
</head>
<body>
    {{> todos}}
</body>

But since this code isn’t being passed through Iron Router, it’ll appear on every page of the application – even when we’d rather it didn’t.

To fix this, delete the above block of code from the HTML file.

Iron Router automatically adds the head and body tags to every route, so nothing will break, and you’ll end up with routes that resemble the following:

The home page will become blank, but we’ll fix that in the next section.

Route Options

To create a home page for this application, create the following template inside the HTML file:

<template name="home">
    <p>Welcome to the Todos application.</p>
</template>

(We’re still going to use the “todos” template, but only for the individual list pages. For the home page, we’ll just use this static template.)

Then, inside the JavaScript file, define the following route:

Router.route('/');

As we can see, to define a route for the home page, we just pass through a forward slash to represent the root URL of the application.

But there’s a problem:

We want to display the “home” template on the home page, but there’s no way for Iron Router to guess that the “home” template is meant to be attached to the “/” path. As such, we have to manually define the association.

To achieve this, pass a pair of curly braces into the Router.route function:

Router.route('/', {
    // options for the route
});

Between these braces, we can define a number of options that affect how the route operates. This gives us a lot of flexibility, but we don’t have to think too much about what’s possible just yet.

For the moment, all we want to do is tell the route which template should be associated with the “/” path.

This can be done by defining a “template” option in the JSON format:

Router.route('/', {
    template: 'home'
});

Here, we’re passing through the name of the “home” template, so when we visit the root of the application, the “home” template will now appear:

Application Layouts

A useful feature of Iron Router is the ability to define application-wide layouts. This means we can define a header and a footer that will appear on every page of the application without manually including any templates.

To see this in action, create a “main” template, which will act as the layout:

<template name="main">
    <h1>Todos</h1>
    <hr />
    <p>Copyright &copy; Todos, 2014-2015.</p>
</template>

Here, we’ve created a header and a footer within the same template.

Then, to set this template as the application’s layout, add the following block of code to the top of the JavaScript file:

Router.configure({
    // options go here
});

Within this Router.configure block, we can define a number of options that will apply to all of the routes within our project.

In this case, we want to define the “layoutTemplate” option:

Router.configure({
    layoutTemplate: 'main'
});

Here, we’re passing through the name of the “main” template, and after saving the file, notice how the template appears on every page of the application:

But there’s a problem:

The content of each route’s template has stopped appearing. No matter which route we visit, we can only see the content from the “main” template.

To fix this, we need to modify the “main” template and define where the content of a route should appear within the layout.

This can be achieved with a special “yield” keyword:

<template name="main">
    <h1>Todos</h1>
    {{> yield}}
    <hr />
    <p>Copyright &copy; Todos, 2014-2015.</p>
</template>

Because of this change, the content of each route will now appear within the layout, between the header and the footer. You can navigate between the pages to see the layout remain consistent while the content of the pages change.

This is the home page, for instance:

…and this is the “Register” page:

Linking Routes

Now that we have a handful of routes, we should start linking those routes together so users can navigate between the different pages.

To achieve this, we could use hard-coded links:

<a href="/register">Register</a>

But if we change the path of the “/register” route, this link will break, so the hard-coded approach will quickly become a pain to manage.

Luckily there is a more elegant solution.

First, we can define a name for each route. This name is basically a label that we’re not supposed to change. So even if the route’s path or template is modified, the name of the route will remain the same.

Second, we can use a special Spacebars syntax to dynamically generate links by referencing the name of the route, rather than the path of the route.

To implement this, return to the route for the home page:

Router.route('/', {
    template: 'home'
});

…and pass through a “name” option:

Router.route('/', {
    name: 'home',
    template: 'home'
});

Here, we’ve named this route “home”.

To generate a link that relies upon this name, create a hard-coded link within the “main” template:

<a href="#">Home</a>

But replace the contents of the href attribute with the following syntax:

<a href="{{pathFor route='home'}}">Home</a>

Here, we’re using this pathFor tag, which is provided to us by Iron Router, and passing through the name of a route – in this case, the “home” route.

As a result, the link will continue to work if the path of the route ever changes. The link will only break if the name of the route is changed.

But what about the “register” and “login” routes?

Do we have to define names for them?

Nope.

If we don’t define a name for a route, the name of the route will be equal to the path of that route. So the “/register” route will be called “register” and the “/login” route will be called “login”.

You might still want to define names for these routes, but it’s basically a matter of asking: How likely is it that the path of these routes will change? If the answers is unlikely, then defining a name might not be necessary.

To finish building the navigation, create a “navigation” template:

<template name="navigation">
</template>

…and fill it with an unordered list of links to our various routes:

<template name="navigation">
    <ul>
        <li><a href="{{pathFor route='home'}}">Home</a></li>
        <li><a href="{{pathFor route='register'}}">Register</a></li>
        <li><a href="{{pathFor route='login'}}">Login</a></li>
    </ul>
</template>

Then include this template within the “main” template:

<template name="main">
    <h1>Todos</h1>
    {{> navigation}}
    {{> yield}}
    <hr />
    <p>Copyright &copy; Todos, 2014-2015.</p>
</template>

The reason we’re moving the navigation into a separate template is because, later on, the navigation will refresh whenever the user logs into and out of the application, and when that happens, we don’t want to refresh the “main” template (as that would be a wasteful performance burden).