Web Front-end16 minute read

Meteor Tutorial: Building Real-Time Web Applications with Meteor

Meteor makes developing web applications simple. It’s easy to learn, and comes with a pre-built arsenal of powerful functionalities. In this article, we will walk through a detailed tutorial for building a simple real-time web application.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

Meteor makes developing web applications simple. It’s easy to learn, and comes with a pre-built arsenal of powerful functionalities. In this article, we will walk through a detailed tutorial for building a simple real-time web application.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Peter Rogers
Verified Expert in Engineering

Peter (BCSc) is a front-end web and mobile developer with 20+ years of programming experience, most recently focused on JavaScript and PHP.

PREVIOUSLY AT

Surge
Share

Meteor is a full-stack JavaScript framework for web and mobile applications. It has been around since 2011, and has gained a strong reputation among Meteor developers for being an ideal, easy-to-use solution for rapid prototyping. However, lately developers have realized Meteor isn’t just for prototyping anymore: it’s ready to be used for commercial development. With the arsenal of packages that it provides, the solid mongoDB/node.js foundation that it relies on, and the coding flexibility that it offers; Meteor makes it easy to build robust, secure real-time web applications, handling everything from the browser app to the server or database.

meteor tutorial

This Meteor tutorial will walk you through making a basic web application in Meteor - a simple catalog that lets users login and manage a list of books.

Why use Meteor? The short answer is, “because Meteor is fun”. It makes developing web applications simple. It is easy to learn, and it lets you focus more on the functionality of your application than the basics of syncing data and serving pages.

It also has lots of behaviors conveniently built in. Meteor automatically performs live updates, so data changes show up immediately in your browser window, and even code changes to the application itself are pushed to all browsers and devices in “real-time”. Meteor has built-in latency compensation, is easy to deploy, and has easy-to-install “packages” that handle all sorts of functionality.

Even though it’s a relatively new framework, lots of startups are already building Meteor apps, including relatively large-scale services like Respondly and Telescope.

Meteor Installation and Scaffolding

Installing Meteor on *nix systems is a one-liner:

curl https://install.meteor.com/ | sh

Although there is still no official support, their preview for Windows is coming along nicely. Rumors are that Windows support will ship with 1.1, due out in April or May of 2015. As you may expect from a smart framework like Meteor, bootstrapping an application requires a single line of command to be invoked:

meteor create book-list

This will create a directory named “book-list”, and populate it with some boilerplate and dependent code. To run the application, enter the newly created directory and execute:

meteor

Open http://localhost:3000 in your web browser, and you’ll see the following:

welcome to meteor

You can also check out the “version 0” of our application on MeteorPad, a site that’s like JSFiddle for Meteor: Book List: Default App

Meteor stores its views in HTML files. If we open “book-list.html”, we will see:

<head>
  <title>book-list</title>
</head>

<body>
  <h1>Welcome to Meteor!</h1>

  {{> hello}}
</body>

<template name="hello">
  <button>Click Me</button>
  <p>You've pressed the button {{counter}} times.</p>
</template>

Meteor uses “Blaze”, a templating engine, to render responses from these HTML files. The double braces should be familiar to anyone who has used Handlebars.js (or other similar templating engines), and they serve a similar function here. Blaze examines the expressions inside each pair of double braces and replaces each one with the value that these expressions yield.

This simple example program has just two double-braced expressions:

  • The first, “{{> hello}}”, tells Blaze to include a template called “hello”. That template is defined at the bottom of the file, in the <template name=”hello”> section.

  • The second, “{{counter}}”, is a little more complicated. To see where this “counter” value is coming from we need to open “book-list.js”:

if (Meteor.isClient) {
  // counter starts at 0
  Session.setDefault('counter', 0);

  Template.hello.helpers({
    counter: function () {
      return Session.get('counter');
    }
  });

  Template.hello.events({
    'click button': function () {
      // increment the counter when button is clicked
      Session.set('counter', Session.get('counter') + 1);
    }
  });
}

if (Meteor.isServer) {
  Meteor.startup(function () {
    // code to run on server at startup
  });
}

Some things require explaining here. First, the split into “if (Meteor.isClient)” and “if (Meteor.isServer)”. Recall that meteor is a full-stack framework, so the code that you write is run on both the server and the client. These conditionals allow us to limit that: the first block runs only on the client, and the second one runs only on the server.

Second, there’s the “Session.setDefault” call - this initializes a session variable called “counter” in the browser. Session variables act a bit like globals in Meteor (for good and for ill). However, that session variable is not showing up directly in “{{counter}}”. Instead, that “counter” expression is a “helper”, one that’s defined in the “Template.hello.helpers” section. This helper simply grabs the session variable’s value and returns it.

Note that the helper is “reactive”. This means that any time the session variable changes, Meteor automatically re-runs this helper function that’s referencing it, and Blaze automatically updates the browser with the new content.

The client code also monitors events, via “Template.hello.events”. We identify the event by event type and selector (in this case, “click button”), and then tell Meteor what that event should do. In this case, the session variable is incremented, which re-runs the helper function and in turn re-renders the content.

Displaying Static Data

All of this is well and good, but it’s not the Meteor application we want for this tutorial.

Let’s start tweaking this application - we’ll display a static, hard-coded list of books. For now, we’ll stash the list of books in a session variable. In the “isClient” code, we’ll use “Template.hello.rendered” to set the session variable as soon as the bookList template is rendered:

  Template.hello.rendered = function() {
    Session.setDefault('books', [
      {title: "To Kill a Mockingbird", author: "Harper Lee"},
      {title: "1984", author: "George Orwell"},
      {title: "The Lord of the Rings", author: "J. R. R. Tolkien"},
      {title: "The Catcher in the Rye", author: "J. D. Salinger"},
      {title: "The Great Gatsby", author: "F. Scott Fitzgerald"}
    ]);
  };

Then, we return that session variable with a new helper in the “hello” template:

  Template.hello.helpers({
    books: function () {
      return Session.get('books');
    }
  });

And display it on screen via variable interpolation in the “hello” template:

<template name="hello">
  <h3>Here are your books:</h3>
  {{ books }}
</template>

You can check out this code in action on Meteorpad: Book-List: Show Session Variable

The first thing to notice is that the Meteor server automatically detected the changes to our codebase, pushed the new code to the clients, and prompted the client to reload. Even after we’ve deployed an application, we can deploy changes and automatically update our clients via hot-code pushes.

deploy changes automatically

So far, this is what we get:

incorrect data display

Oops, we’re displaying the data wrong. Blaze gets points for accuracy here (why yes, it is an array of objects), but we’re going to have to be a bit more clever if we want to display our list of books in a useful way. Fortunately, Blaze makes it very easy to work with arrays of data using the “#each” directive:

 <h3>Here are your books:</h3>
  <UL>
    {{#each books}}
        <LI><i>{{title}}</i> by {{author}}</LI>
    {{/each}}
  </UL>

In Blaze, “#each” works a bit like Angular’s “ng-repeat” directive - it iterates through the array structure, setting the current context to the current object in the array, and repeatedly displaying the HTML inside the “{{#each …}}”. This is what our list of books look like now:

correct data display

On MeteorPad: Show Session Variable Properly

Some Cleaning Up

Before going any further, let’s clean up our code a little.

Meteor allows tremendous leeway in how you organize your codebase. As you will see, there are only a few hard-and-fast rules: wherever you put your HTML and JavaScript, Meteor will find it. This flexibility is nice, but it does mean it’s more incumbent on you to organize your code in a way that makes sense, so you’re not stuck maintaining a giant disordered mess.

First, let us rename this “hello” template to something meaningful, like “bookList”, and replace the boilerplate HTML with this:

<head>
  <title>book-list</title>
</head>

<body>
  {{> bookList}}
</body>

<template name="bookList">
  <h3>Here are some books:</h3>
  <UL>
    {{#each books}}
        <LI><i>{{title}}</i> by {{author}}</LI>
    {{/each}}
  </UL>
</template>

Second, let us split up the “client” and “server” parts into separate files. In our application directory, we’ll set up a “client” subdirectory and a “server” subdirectory - Meteor will know automatically to run the “/client/” files on the client, and run the “/server/” files on the server. It’s a good convention to put template code in a JavaScript file named after the template, so let’s put our client code into “client/bookList.js”. We can put our currently empty server-startup code in “server/startup.js”. Finally, let’s move out the “

Note that even after all this switching around, Meteor will still automatically find all of our HTML and JavaScript files. So long as a file is somewhere in “/client/”, Meteor will know to run it on the client. So long as a file is somewhere in “/server/”, meteor will know to run it on the server. Again, it is up to the developer to keep things organized.

So now our code should look like this:

book-list.html:

<head>
  <title>book-list</title>
</head>

<body>
  {{> bookList}}
</body>

client/bookList.html:

<template name="bookList">
  <h3>Here are some books:</h3>
  <UL>
    {{#each books}}
        <LI><i>{{title}}</i> by {{author}}</LI>
    {{/each}}
  </UL>
</template>

client/bookList.js:

Template.bookList.rendered = function() {
  Session.setDefault('books', [
    {title: "To Kill a Mockingbird", author: "Harper Lee"},
    {title: "1984", author: "George Orwell"},
    {title: "The Lord of the Rings", author: "J. R. R. Tolkien"},
    {title: "The Catcher in the Rye", author: "J. D. Salinger"},
    {title: "The Great Gatsby", author: "F. Scott Fitzgerald"}
  ]);
};

Template.bookList.helpers({
  books: function () {
    return Session.get('books');
  }
});

server/startup.js:

Meteor.startup(function () {
    // code to run on server at startup
});
 ~~~

Check it out on MeteorPad: [Initial Code Cleanup](http://meteorpad.com/pad/MwvMcsBAzfbWwEXp3/Book-List:%20Initial%20Code%20Cleanup)

Verify that everything's running correctly by checking the browser window and then we're good to move on to the next step.

## Using the Database in Meteor

The Meteor server runs on top of a MongoDB database. In this section of our tutorial, we will move the static list of books out of the session variable and into that database.

First, delete the Template.bookList.rendered code, so that we're no longer putting stuff into that session variable. Next, we should add that list of books to the database as fixture data when the server initializes. As you'd expect for MongoDB, Meteor stores data in "collections". So, we'll create a new collection for our books. To keep things simple we will name it "books". It turns out that both the client and the server will want to know about this collection, so we'll put this code in a new subfolder: "/lib/". Meteor knows automatically that files in "/lib/" run on the client and the server.

We'll create a file called "lib/collections/books.js", and give it just one line of code:

~~~ js
Books = new Meteor.Collection("books");

Within the browser window pointed at http://localhost:3000, go to the developer console and check the value of “Books”. It should now be a Mongo collection! Try running “Books.find().fetch()”, and you’ll get an empty array – which makes sense, since we haven’t added any books to it yet. We can try adding items to it in the console:

Books.insert({title: "To Kill a Mockingbird", author: "Harper Lee"})

Adding things in the console is rather tedious. Instead, we’ll set things up so that when the server starts up we automatically insert fixture data into the database. So let’s go back to “server/startup.js”, and add this:

Meteor.startup(function () {
    if (!Books.findOne()) {
        Books.insert({title: "To Kill a Mockingbird", author: "Harper Lee"});
        Books.insert({title: "1984", author: "George Orwell"});
        Books.insert({title: "The Lord of the Rings", author: "J. R. R. Tolkien"});
        Books.insert({title: "The Catcher in the Rye", author: "J. D. Salinger"});
        Books.insert({title: "The Great Gatsby", author: "F. Scott Fitzgerald"});
    }
});

Now, when the server starts up, if there’s no data present we’ll add the fixture data. We can verify this by going back to our terminal, stopping the meteor server, and running this command:

meteor reset

Note: you will rarely need this command, because it resets - i.e., clears - the database Meteor is using. If your Meteor application has any user data in the database, you shouldn’t run this command. But in this case, we’ll just clear out whatever test data we have.

Now we’ll start the server again:

meteor

On startup, Meteor will run the startup routine, see that the database is empty, and add the fixture data. At this point, if we go to the console and type “Books.find().fetch()”, we get the five books we had before.

All that’s left for this step is to display the books on screen. Fortunately, that is as simple as replacing “return Session.get(‘books’);” with the following in “books” helper:

return Books.find();

And we’re back in business! The application is now displaying data from a database cursor, instead of from the session variable.

Check it out on MeteorPad: Moving to the Database

Security Concerns

I’ll preface this by saying: “don’t do this”.

What do you think would happen if somebody fired up this app in their browser, went to the console, and typed in “Books.remove({})”?

The answer is: they’d wipe out the collection.

So, that’s a pretty large security problem - our users have too much access to our database. Any client can access the whole database. Not only that, any client can make any change to the whole database, including that “.remove({})” data wipeout.

This is not good, so let’s fix it.

Meteor uses so-called “packages” for adding functionality. What modules are to Node.js, and gems are to Ruby, packages are a bundled goodness of functionality for Meteor. There are packages for all sorts of things. To browse through what’s available, check out atmosphere.js.

The default meteor app that we made with “meteor create” includes two packages called “autopublish” and “insecure”. The first package makes it so that clients have automatic access to the whole database, and the second makes it so that users can perform any actions on that database.

Let’s remove those. We can do that by running the following from the app directory:

meteor remove autopublish insecure

When that’s done, you’ll see the book-list data disappear from the screen (since you no longer have access to it), and if you try a “Books.insert” call, you’ll get the error: “insert failed: Access denied”. On MeteorPad: Security Overkill

If you take nothing else from this Meteor tutorial, please remember this: when you’re deploying a Meteor app, be sure you remove the packages autopublish and insecure. Meteor has many good security precautions, but all of them are for naught if you leave those two packages installed.

So, why does Meteor automatically include these packages at all, if they’re such a security hazard? The reason is that, especially for beginners, those two packages make it easier to get started - you can easily debug and tweak the database from a browser’s console. But it’s a good practice to ditch autopublish and insecure as soon as possible.

Publish and Subscribe

So we fixed that gaping security hole, but we’ve introduced two problems. First, we now have no access to the database. Second, we have no way of interacting with the database.

Let’s tackle the first problem here. Meteor provides secure access to a database by having the server “publish” a subset of the database, and having the client “subscribe” to that publication.

First, let’s create “/server/publications.js”:

Meteor.publish('books', function() {
  return Books.find({});
});

And we’ll create “/client/subscriptions.js”:

Meteor.subscribe('books');

Check it out on MeteorPad: Publish and Subscribe

The server “publishes” a cursor that has access to all the data, and the client “subscribes” to it on the other end. The client uses this subscription to populate a mirrored copy of the database with all of the cursor’s data. When we access “Books.find().fetch()”, we see all five objects, and we see them displayed on screen as before.

The difference now is that it’s really easy to limit what the clients can access. Try switching the publication “find()” to a subset of the data:

Meteor.publish('books', function() {
  return Books.find({}, {limit:3});
});

Now the client only ever sees three of the five books, and there’s no way to get to the rest. Not only is this a great boon for security (I don’t get to see everybody else’s bank accounts), but you can use it to parcel out data and avoid overloading a client.

Adding New Books

We’ve seen how to give clients read-access to the database in a limited, secure way. Now, let’s look at the second problem: how do we let users alter the database without allowing them to do anything they want? Getting rid of the package insecure made it so clients have no access at all - let’s try allowing the addition of books again. In meteor, we do this by adding a “method” to the server. Let’s add a method, one that adds a new book, to “/lib/collections/books.js”:

Meteor.methods({
  addBook: function(bookData) {
    var bookID = Books.insert(bookData);
    return bookID;
  }
});

As you can see, this takes in “bookData” - in this case, that’s an object with “title” and “author” fields - and adds it to the database. Once your client reloads, we can then call this method from the client. You can go to the console and enter something like this:

Meteor.call('addBook', {title: "A Tale of Two Cities", author: "Charles Dickens"})

And presto! You get another book on the list of books. Using the console is intolerably clunky, so let’s go ahead and add a simple form to the end of the “bookList” template that will let us add new books:

<form class="add-book">
	Title:<br>
	<input type="text" name="title">
	<br>
	Author:<br>
	<input type="text" name="author">
	<input type="submit" value="Add Book">
</form>

And we can hook that up to JavaScript using an event case, like we had in the original test application:

Template.bookList.events({
  "submit .add-book": function(event) {
    event.preventDefault(); // this prevents built-in form submission
    Meteor.call('addBook', {title: event.target.title.value, author: event.target.author.value})
  }
});

You can see this in action on MeteorPad: Methods

When we had insecure and autopublish in place, clients could access and alter the whole database. Now with insecure and autopublish gone, but with publications, subscriptions, and methods, clients can access the database and interact with it in a controlled manner.

On a side note: you can also address security issues in Meteor by using “allow and deny rules”. You can find out more about those and some reasons I prefer the above approach instead at discovermeteor.com.

User Authentication

It may look like we’ve just circled back around to where we started, but there’s one important difference: it’s really easy for us to now limit access to the database. To see how this works, let’s try adding users to this app. We’ll add a login system to our app, and then instead of having all clients work with one system-wide list of books, we’ll make it so each user can only add to or read their own list of books.

Go to the app directory, and install two packages:

meteor add accounts-ui accounts-password

There. You’ve just added a login system to the app. Now we just need to add the login UI to book-list.html. Put this single line at the top of the body:

{{> loginButtons}}

You should see a login prompt at the top of the screen:

login prompt

Notice that if you click the login link, it’ll ask for an email address and password. We can switch that to simple username/password login system by creating “/client/config.js” with the following in it:

Accounts.ui.config({
    passwordSignupFields: "USERNAME_ONLY"
});

At this point, you can type “Meteor.userId()” in the console, and it will return “null”. You can try clicking the link to create an account. Calling “Meteor.userId()” should now return an ID string. The server has access to this same piece of information (as “this.userId”), so it’s trivial to make the “add books” method force the user to be logged in and include a userID field:

Meteor.methods({
  addBook: function(bookData) {
    if (this.userId) {
        bookData.userID = this.userId;
        var bookID = Books.insert(bookData);
        return bookID;
    }
  }
});

All that remains now is to limit the client, showing only the books that this user has added. We use the publication’s ability to narrow down what the client has access to:

Meteor.publish('books', function() {
    return Books.find({userID: this.userId});
});

Now, the publication only finds books from this particular user. We can even access the userId from Blaze expressions as “{{currentUser}}”; and we can use this, with an “{{#if}}” directive (which does exactly what you think it does), to only show data when the user is logged in:

<template name="bookList">
  {{#if currentUser}}
    <h3>Here are your books:</h3>
    <UL>
      {{#each books}}
          <LI><i>{{title}}</i> by {{author}}</LI>
      {{/each}}
    </UL>
    <form class="add-book">
    	Title:<br>
    	<input type="text" name="title">
    	<br>
    	Author:<br>
    	<input type="text" name="author">
    	<input type="submit" value="Add Book">
    </form>
  {{else}}
    <h3>Please log in to see your books</h3>
  {{/if}}
</template>

Check out the final result on MeteorPad: Users

Deployment

Now we can deploy this Meteor application to the Internet. We do this by going to the application directory on a terminal and executing:

meteor deploy <your app's name>.meteor.com

Make sure you replace “<your app’s name>” with an actual, short name for the application instance. Running this command will prompt you to set up an account with Meteor, and then it will put your new application on Meteor’s test servers so that you can try it out on the Internet.

For a quick demo, this meteor.com solution is all you need. The Meteor team hasn’t announced any explicit limits to storage or bandwidth on their servers. The only notable limitation is that if your app goes unused for a long time, the site takes a few seconds to spin up for the next user.

That said, meteor.com is not intended for commercial use. But when you go to production, there are platform-as-a-service companies like Modulus and Digital Ocean that make deploying Meteor apps easy. Should you want to deploy a meteor app to your own server, “meteor up” makes that process straightforward, too.

Next Steps

Congratulations! In your quest to learn Meteor you’ve now made and deployed a very simple real-time Meteor web application. Obviously, this is just a tiny first step into a whole universe of features and functionality. If you like what you’ve seen so far, I highly recommend Your First Meteor Application by David Turnbull, which walks readers through creating a more complicated application, with more information about meteor features along the way. It’s available as a Kindle book for a small price, and as a free PDF on Turnbull’s web site.

You’ll also want to explore the packages that are available for Meteor. Nine times out of ten, the answer to “how do I do <x> in Meteor?” is “there’s a package for that”. “How do I add routing to my application?” You use Iron Router. “How do I provide a RESTful API?” You use RESTivus. “How do I include unit testing?” You use Velocity. “How do I add schema validations?” You use Collection2. You can easily get lost on Atmosphere.js looking at all the available packages.

Why Not Use Meteor?

As you can see from this tutorial, Meteor is simple and fun to write applications on, but I’d be remiss if I didn’t mention the drawbacks.

Meteor is still relatively immature. It hit 1.0 this past October, and that leads to some problems. If you’re looking to do something obscure, it’s possible nobody’s written a package for that functionality yet. The packages that do exist are more likely to have bugs, simply because they haven’t been around long enough to suss out all of the problems.

Scaling can also be somewhat unknown with Meteor. There are plenty of Meteor sites that scale up to reasonable amounts of users, but few very large sites - nothing on the order of Facebook or LinkedIn, with tens or hundreds of millions of users.

However, for most applications Meteor is a perfect choice, as it is a chance to cut down on development time and to participate in the start of something great.

Hire a Toptal expert on this topic.
Hire Now
Peter Rogers

Peter Rogers

Verified Expert in Engineering

Austin, TX, United States

Member since February 15, 2015

About the author

Peter (BCSc) is a front-end web and mobile developer with 20+ years of programming experience, most recently focused on JavaScript and PHP.

authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

PREVIOUSLY AT

Surge

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

Join the Toptal® community.