Chapter 10: Accounts

Accounts

The biggest problem with our application at the moment is that it only supports a single list of players. This means there can only be one user of the application at a time, which isn’t useful for an application that’s designed for the web.

To fix this, we’re going to add a user accounts system to our project, which is actually one of the simplest things we can do with the Meteor framework.

With this system in place:

  1. Users will be able to register and login.
  2. Unregistered users won’t be able to see parts of the interface.
  3. Every registered user will have their own, unique leaderboard.

It’s a lot of functionality but it doesn’t take a lot of code.

Login Providers

At this point, we’re going to add another package to our project (you might recall that we added the “session” package in an earlier chapter).

In this case, we’re going to install what’s known as a “login provider” package, and these packages make it extremely easy to add a back-end for a user accounts system to any project.

When working with most frameworks, for instance, creating a user accounts system might start by creating place to store the data of users:

UserAccounts = new Mongo.Collection('users');

Then writing application logic for registration, logging-in, recovering lost passwords, and all those other standard features of account systems.

But when working with Meteor, all we have to do to create a user accounts system is switch to the command line and write the following command:

meteor add accounts-password

Here, we’re adding this “accounts-password” package to the project. This package creates the back-end for an accounts system that relies on an email and password for registration and logging-in.

Specifically, this package:

  1. Creates a collection for storing the data of registered users.
  2. Provides us with a range of useful functions that we’ll talk about soon.

There’s other login provider packages available that allow users to log into our application through external services like Facebook and Google, but since this adds an extra step to the process, we’ll stick with the system that just relies on the email and password for the time being.

Meteor.users

When we added the “accounts-password” package to our project, a collection was automatically created to store the data of registered users. This collection is known as Meteor.users, and it works just like any collection that we might create ourselves.

To demonstrate this, enter the following into the Console:

Meteor.users

Then tap the “Return” key.

The data that’s returned confirms that, yes, this is just a regular collection:

Knowing this, it’s not surprising that we can even use the find and fetch functions to navigate through the collection:

Meteor.users.find().fetch();

But because there are no registered users for the application yet, not data will be returned.

Login Interface

After installing the login provider package, a back-end was created for an accounts system. But we still need to setup an interface that allows users to register and login.

We can create such an interface from scratch, which is still quite simple when working with Meteor, but there’s another approach available to us:

All we have to do is add another package to our project.

This package, which is another official package from the Meteor Development Group, is known as “accounts-ui”, and after adding it to our project, we’ll have access to a fully functional interface for our accounts system.

To install the package, run the following command:

meteor add accounts-ui

Then, once the package is installed, switch to the HTML file and including a template named “loginButtons” between the body tags:

{{> loginButtons}}

(You might also want to wrap this tag in p tags to ensure that there’s some padding beneath it.)

This “loginButtons” template is part of the “accounts-ui” package, and by including it somewhere in our interface, we’ve done all we need to do.

Save the file, return to the browser, and notice that a “Sign In” button has appeared inside the interface, and if we click this button, a login form will appear, along with a link that says “Create Account”.

But this is not a “dumb” interface.

Already, without any configuration, it’s possible for users to register for an account, login to that account, and logout.

To see this in action:

  1. Click the “Sign In” button.
  2. Click the “Create Account” button.
  3. Enter an email and password.

Then submit the form. You’ll be logged-in automatically after registration.

Of course, right now, there’s no reason for a user to register or login – it doesn’t offer any additional functionality – but that’s something we’ll work on for the rest of this chapter.

For the time being, use the find and fetch functions on the “Meteor.users” collection:

You’ll see that a document is returned, and this document contains the data of the account we just created. You can click the downward facing arrows to see the data associated with the account, such as the email address.

Logged-in Status

At the moment, unregistered users can see parts of the interface they shouldn’t be able to see, like the “Add Player” form and the list of players in the leaderboard.

Because of the packages we’ve installed, this is something we can fix with very few lines of code.

To begin, logout of the user’s account.

Then return to the “leaderboard” template in the HTML file and wrap the contents of the templates in the following conditional:

{{#if currentUser}}
    <!-- leaderboard code goes here -->
{{/if}}

The template’s code should then resemble:

<template name="leaderboard">
{{#if currentUser}}
    {{> addPlayerForm}}
    <ul>
    {{#each player}}
        <li class="player {{selectedClass}}">{{name}}: {{score}}</li>
    {{/each}}
    {{#if selectedPlayer}}
        <li>Selected Player: {{selectedPlayer.name}}</li>
        <li>
            <button class="increment">Give 5 Points</button>
            <button class="decrement">Take 5 Points</button>
            <button class="remove">Remove Player</button>
        </li>
    {{/if}}
    </ul>
{{/if}}
</template>

Here, we’re using the Spacebars syntax to create a conditional that checks whether or not currentUser returns true.

If the current user of the web application is logged-in, then currentUser will return true, but if the current user of the web application is not logged-in, then currentUser will return false.

As such, with just a couple lines of code, we’ve made it so only logged-in users can see (and interact with) the form.

But we can also display content that only appears for logged-in out users, which is a useful way to inform users that they need to be logged-in to make the most of the web application.

To do this, add an else clause to the conditional:

<template name="leaderboard">
{{#if currentUser}}
    {{> addPlayerForm}}
    <ul>
    {{#each player}}
        <li class="player {{selectedClass}}">{{name}}: {{score}}</li>
    {{/each}}
    {{#if selectedPlayer}}
        <li>Selected Player: {{selectedPlayer.name}}</li>
        <li>
            <button class="increment">Give 5 Points</button>
            <button class="decrement">Take 5 Points</button>
            <button class="remove">Remove Player</button>
        </li>
    {{/if}}
    </ul>
{{else}}
    <p>You must be logged-in to see this content.</p>
{{/if}}
</template>

When logged-out, this is what users will see:

One Leaderboard Per User

To make our application useful, we need to make it so registered users each have their own, independent list of players.

It might not be immediately obvious how we go about doing this, and the most difficult part of programming is figuring out how to approach such problems, but the process itself doesn’t involve a lot of steps.

To begin, return to the “submit form” event inside the JavaScript file, and create a variable named “currentUserId”:

var currentUserId;

Then make this equal to Meteor.userId():

var currentUserId = Meteor.userId();

The entire event should then resemble:

'submit form': function(event){
    event.preventDefault();
    var playerNameVar = event.target.playerName.value;
    var currentUserId = Meteor.userId();
    PlayersList.insert({
        name: playerNameVar,
        score: 0
    });
    event.target.playerName.value = "";
}

We haven’t talked about the Meteor.userId() function before, but there’s not much to explain. This function, which is provided to us by the login provider package we installed earlier, allows us to retrieve the unique ID of the currently logged-in user (or, if the current user is not logged-in, then the function returns null).

Next, add a “createdBy” field to the insert function within this event, and pass through the “currentUserId” variable as the value:

PlayersList.insert({
    name: playerNameVar,
    score: 0,
    createdBy: currentUserId
});

Because of this code, when a user adds a player to the leaderboard, the unique ID of that user will be associated with the player’s document.

To demonstrate this:

  1. Save the file.
  2. Return to the browser.
  3. Add a player to the leaderboard. (You’ll need to be logged-in.)

Then use the find and fetch functions on the “PlayersList” collection, and click the downward-facing arrow for the most recently created document:

You’ll see how this document contains the ID of the user who added this player to the collection.

Next, we’re going to modify the “player” helper function:

'player': function(){
    return PlayersList.find({}, { sort: {score: -1, name: 1} });
}

We’ll start by setting up another “currentUserId” variable:

'player': function(){
    var currentUserId = Meteor.userId();
    return PlayersList.find({}, { sort: {score: -1, name: 1} });
}

Then we’ll change the return statement so it only returns players when their “createdBy” field is equal to the ID of the currently logged-in user:

'player': function(){
    var currentUserId = Meteor.userId();
    return PlayersList.find({ createdBy: currentUserId },
                            { sort: {score: -1, name: 1} });
}

(You can see that I’ve spread the Mongo query over two lines. This is is a useful thing to do as a query grows in complexity, and because white-space is ignored, it doesn’t affect how the code is run.)

The code we’ve written ensures that users only see players they added to the leaderboard, thereby creating the effect that each user has their own, unique list of players.

Project Reset

Because of the change we’ve just made, we have a number of players in our database who aren’t attached to any particular user, meaning we don’t need them inside the “PlayersList” collection.

We could manually remove this data, but instead, we’ll do something that’s quite common during the development process, and wipe the slate clean in a single fell swoop.

To do this, switch to the command line, stop the local server with the CTRL + C keyboard shortcut, and run the following command:

meteor reset

This command will destroy all of the data in the database, and because of the code we wrote in the previous section, all players added to the database from this point onward will be attached to a user.

Summary

In this chapter, we’ve learned that:

  • “Login Provider” packages are used to create the back-end of an accounts system. We can create a back-end that relies on an email and password, or that integrates with services like Twitter, Facebook, and Google.
  • After installing a login provider package, a Meteor.users collection is created, which allows us to store the data of registered users.
  • The “accounts-ui” package provides us with a default set of user interface elements for registering and logging into an account.
  • The we can check whether or not the current user is logged-in by referencing the currentUser object from inside a template.
  • To retrieve the unique ID of the logged-in user, we can use the Meteor.userId() function.

To gain a deeper understanding of Meteor:

  • Install a different login provider package, like the “accounts-twitter” package, and follow the instructions that appear within the browser.

To browse the project’s code in its current state, click here.