Learn Meteor.js From Scratch: Build a Polling App

What is Meteor?

Meteor.js is an open-source platform built on Node and MongoDB. It’s not just a framework, it’s more than that.

Is it comparable to Angular? Angular handles just the frontend parts of application. Meteor is more of a platform than Angular. It is both server and client-side code and handles a lot of the parts of applications that we need to create manually in Angular.

We’ll be diving into a simple application in this tutorial to show off the benefits and power of Meteor.

meteor-homepage

Important Things to Know

  • Real time built into its core: Meteor handles all of the real-time components so that as soon as you update something in your database, that change is made to all other connected users. Out of the box and very easy to use.
  • Full Stack Reactivity. In Meteor, realtime is the default. All layers, from database to template, update themselves automatically when necessary.
  • Built-in build system Meteor believes that we spend too much time packaging our applications together and getting all the dependencies working together nicely. You don’t have to fiddle with a Gulp configuration anymore. Meteor handles it all for you out of the box.
  • Great for single page apps and mobile
  • Packages are handled through Meteor’s pacakage management site: atmosphere.js (can also use npm and cordova packages)
  • Connecting to external services and APIs is possible using ddp: DDP, the Distributed Data Protocol, is a simple protocol for fetching structured data from a server, and receiving live updates when that data changes. We won’t deal with this in our application today, but it’s good to know.

What We’ll Be Building

We’ll be creating a very simple polling application where a user can:

  • Create a poll with 3 choices
  • Vote on polls
  • New polls and votes will show real-time

View the Demo

Let’s create our first Meteor app!

Getting Started

Install Meteor

  • download for windows: https://install.meteor.com/windows
  • install for osx/linux: curl https://install.meteor.com/ | sh

install-meteor-windows

  • test to make sure everything is installed (windows users only cmd)
meteor --version

meteor-version

Demo Apps

Meteor comes with a few demo applications that you can try out. If you want to check out a few demo apps now, you can go into your command line and grab the demo application using:


// todo sample app
meteor create --example todos

// example mobile application
meteor create --example localmarket

You can find the full list of examples in the Meteor GitHub.

Once you create a demo app, just cd into that project and run the application with:

meteor

This will grab the necessary packages, bundle all the css and js, start your application using a Node server, and make it viewable in your browser.

http://localhost:3000

Here is the sample Todos application which is quite good looking:

meteor-demo-todos

Definitely click around the files in these demo applications and you’ll see how Meteor apps tick. Let’s move onto creating our own application now.

Create a New Application

With our newly installed Meteor CLI tools, we can easily create a new application with:

meteor create polling

meteor-create-new-app

polling will be the name of our new application and it will be created in a new polling/ folder. This command will create 3 new files and a folder:


| .meteor/              // holds information about our project
| polling.css       // the css for our project
| polling.html      // the overall template
| polling.js            // javascript for the client and server

This is a very barebones setup and will not be how we structure our application, but it is useful to see how a very simple application can be built.

You can run this app by cding into your new project and starting your meteor app with:

meteor

Then you can view it in the browser at http://localhost:3000

meteor-new-app-barebones

If you take a quick look into polling.js you’ll see some JavaScript bindings to the Template (which is referencing our polling.html file). What you’ll also find is two interesting lines of code that are how Meteor separates code that will be run on the server vs the client.


if (Meteor.isClient) {
    // code here will only be run on the client
}

if (Meteor.isServer) {
    // code here will only be run on the server
}

While we could build our whole application like this, with client and server code in the same file, I prefer to have our application structured so that we don’t have to worry about where our client and server code is.

Keep that meteor command running for the duration of this tutorial. As we make changes to our application, Meteor will automatically restart the server, rebundle our files, and livereload our browser (while keeping all our data). Talk about convenient!

Meteor Recommended Application Structure

Luckily, Meteor has a few folders that are designed specifically for organizing our application. These folders are:

  • client/: The client folder is for files that will only be served to the client. Any CSS and JS files in this folder will be automatically bundled and sent to the browser.
  • server/: The folder for all your server side code. Store sensitive logic or data that a client shouldn’t see.
  • public/: By default, Meteor will find the CSS and JS files in your project and bundle them together. Anything in this public folder will be not be bundled by Meteor and will be served directly to the client as-is. If you have an image at public/images/underwater-dogs.jpg, then you can reference that file from HTML using <img src="images/underwater-dogs.jpg">.
  • private/: These files are only accessed by the server through the Assets API. We won’t be dealing with this folder for this tutorial.

What’s cool is that with this structure, we won’t need to defined Meteor.isClient or Meteor.isServer anymore. If a file is in that folder, then Meteor knows which side of our application it belongs.

With these reserved folders in mind, let’s look at how our application structure will look like:


| .meteor
| client/                       // all the code for our client and browser
    |----- components/          // we'll be creating components for our application parts
      |--- poll-form.css
      |--- poll-form.html
      |--- poll-form.js
      |--- poll.css
      |--- poll.html
      |--- poll.js
    |----- app.body.html        // layout for our entire app
    |----- app.head.html        // document head for entire app
    |----- app.js                   // the overall js for our layout
    |----- app.css                   // the overall css for our layout
| collections/              // here we'll store our mongo models
    |----- polls.js         // defining our mongo collection
| server/                       // code for our server
    |----- bootstrap.js // adding sample data on app startup

client/ will be the folder where we spend most of our time. We’ll go through all of the components, views, CSS/JS in the next section.

client/components/ will hold the different parts of our application. In this case, we just need a form to create polls and a component to show the individual polls. All JS and CSS files in the client folder will be bundled together by Meteor into our application so we’re naming these files is done however we want. They’ll all go to the same place anyway.

server/ will only contain one thing for now. We’ll create a bootstrap.js file to seed our database with some sample data.

collections/ is where we define Mongo collections. In this case, we’ll only need one called polls.js.

routes.js will be in the root of our folder since the routes will be used in both the client and the server.

Take a look at the docs for more on Meteor file structure.

We’re going to start with the server and the collections part of our application first.

Defining a Mongo Collection

MongoDB is very simple to work with and we’ve gone over MongoDB in our article: An Introduction to Mongo. For our purpose for this application, we will be defining a single collection. Let’s do that now.

Create the collections/polls.js file and fill it with:

Polls = new Mongo.Collection('polls');

Easy! The reason we define this in a collections/ folder and not in the client/ or the server/ folder is that this collection will be needed on both the server (creating sample polls) and client (creating new polls).

We are re-using code on the server and client and this is what makes Meteor such a breeze to work with. With the Mongo collection out of the way, let’s create some sample data.

Bootstrapping Our Application and Sample Data

Let’s create some sample data for our application before we start to create the client-side part of our polling application. In the server/ folder, create a new file called bootstrap.js.

Here is the full commented code for bootstrap.js:


// run this when the meteor app is started
Meteor.startup(function() {

  // if there are no polls available create sample data
  if (Polls.find().count() === 0) {

    // create sample polls
    var samplePolls = [
      {
        question: 'Is Meteor awesome?',
        choices: [
          { text: 'Of course!', votes: 0 },
          { text: 'Eh', votes: 0 },
          { text: 'No. I like plain JS', votes: 0 }
        ]
      },
      {
        question: 'Is CSS3 Flexbox the greatest thing since array_slice(bread)?',
        choices: [
          { text: '100% yes', votes: 0 },
          { text: '200% yes', votes: 0 },
          { text: '300% yes', votes: 0 }
        ]
      }
    ];

    // loop over each sample poll and insert into database
    _.each(samplePolls, function(poll) {
      Polls.insert(poll);
    });

  }

});

We are using the Meteor.startup command to run this code when our server starts up. We’re going to check if there are any polls already created in our database and create sample polls if there is nothing in the database.

Quick Note: If you ever want to clear what’s in your database, just run meteor reset and your application will become a clear slate.

meteor-reset

Checking Our Database from the Browser

Since Meteor implements an instance of Mongo on the client, we are able to run MongoDB commands right in our browser’s console. Meteor already restarted our application so that means it should’ve already ran that bootstrap.js file and created our sample polls.

Let’s go into our browser and see our polls. Go into your browser’s console and run the Mongo command to find all the Polls:

Polls.find().fetch()

You’ll see the two polls we created!

meteor-browser-console-find

We now have the foundation we need to start building our polling application’s frontend. We now have:

  • A solid Meteor file structure foundation
  • A Mongo collection to hold our polls
  • Sample data in our database

Let’s move onto the thing that our users will actually see, the UI!

Frontend Foundation and Template

  • app.body.html
  • app.head.html
  • app.js

  • Meteor will find all references of <head> and <body> and combine whatever it finds to one <head> and <body>. So if you have multiple files that have a <body> or <head>, they will all be compiled into your final document.

Check the docs for more on Meteor templating.

Here is what our app.body.html and app.head.html files will consist of:

client/app.body.html


<body>

  <!-- PULL IN THE POLL CREATION FORM -->
  <div class="container">
    <div class="row">
      <div class="col-md-6 col-md-offset-3">
        {{ >pollForm }}
      </div>
    </div>
  </div>  

  <!-- LOOP OVER POLLS AND SHOW EACH -->
  <div class="polls">
      {{ #each polls }}
          {{ >poll }}
      {{ /each }}
  </div>

</body>

{{ >pollForm }} will find a file with <template name="pollForm> within it. We will create this file next. The {{ >templateName }} syntax will be how we pull in different views from our application.

{{ #each polls }} will loop over the polls object and then show the template named poll. We’ll look at how we pull in this polls object from our database soon.

client/app.head.html

Here is the very simple <head> of our app. Meteor will inject all the requirements into this section.


<head>
  <meta charset="utf-8">
  <title>My Polling App!</title>

  <!-- CSS IS AUTOMATICALLY BUILT AND LOADED -->
</head>

Poll Templates

Now that we have the main template down, let’s create the two templates that we referenced: pollForm and poll.

Poll Form (client/components/poll-form.html)

This will store the form for creating polls:


<template name="pollForm">

<form> 
    <div class="form-group">
        <label>Question</label>
        <input type="text" name="question" class="form-control" placeholder="Your Question">
    </div>

    <div class="form-group">
        <label>Choice #1</label>
        <input type="text" name="choice1" class="form-control" placeholder="Choice #1">
    </div>
    <div class="form-group">
        <label>Choice #2</label>
        <input type="text" name="choice2" class="form-control" placeholder="Choice #2">
    </div>
    <div class="form-group">
        <label>Choice #3</label>
        <input type="text" name="choice3" class="form-control" placeholder="Choice #3">
    </div>

    <button type="submit" class="btn btn-lg btn-primary btn-block">Create Poll</button>
</form>

</template>

We’ll just limit users to creating 3 options for now to keep things simple. In the future, you could add a plus button that adds an input for choices.

Here’s some quick CSS to style the question group a little differently than the option inputs. We’ll place this in poll-form.css.


.question-group   {
  margin-bottom:20px;
  background:#EEE;
  padding:20px;
}
.question-group label   {
  font-size:18px;
}

Let’s go back to our browser and see what our cool application looks like now.

poll-form

Pretty lackluster so far. This is because none of our Bootstrap classes will work because we never got Bootstrap CSS and added it to our project. We’ll handle processing this form and then create our poll template. After that we’ll move onto getting Bootstrap.

client/components/poll.html

This will be the poll template for showing off single polls.


<template name="poll">

<div class="poll well well-lg" data-id="{{ _id }}">
    
    <h3>{{ question }}</h3>

    {{ #each indexedArray choices }}
        <a href="#" class="vote btn btn-primary btn-block" data-id="{{ _index }}">
            <span class="votes pull-right">{{ votes }}</span>
            <span class="text">{{ text }}</span>
        </a>
    {{ /each }}
    
</div>
</template>

We will be displaying the question, choices, votes, and text associated with the poll.

How do we get these polls to show? In our original app.body.html file, we referenced the polls using {{ #each polls }}. We are also adding in data-id with the _index so that we’ll be able to know which question to apply our vote to.

Let’s give our body access to this object from our database now.

We can easily assign variables to our body template by going into our app.js file and creating:


Template.body.helpers({
  
  polls: function() {
    return Polls.find();
  }
  
});

Just like we used the Polls collection earlier, we can use it now to grab all our polls. This will give access to the polls object within our body template.

Notice we also added an indexedArray to the choices each. This is because we don’t inherently have the index when we loop over items in Meteor right now. That will probably change in the future, but it isn’t implemented currently because of the reactive nature of the templating engine. Apparently $index in these real-time applications is a harder thing to pull off.

We need to create this indexedArray helper so let’s go back into app.js and add the following:


// adds index to each item
UI.registerHelper('indexedArray', function(context, options) {
  if (context) {
    return context.map(function(item, index) {
      item._index = index;
      return item;
    });
  }
});

Thanks to Jenia Nemzer for the above helper. We now have access to the data-id={{ _index }}. We’ll use this when we implement our voting features.

Now we can see our polls that we created in our bootstrap.js file showing in our application.

polls-showing

Let’s wire up our form now to handle form submission.

Processing the Poll Form

Meteor has a cool way of attaching events and variables to its templates. Currently, we have a pollForm template. Let’s go into the poll-form.js file and add event handlers for our form submission.

client/components/poll-form.js


Template.pollForm.events({

  // handle the form submission
  'submit form': function(event) {

    // stop the form from submitting
    event.preventDefault();

    // get the data we need from the form
    var newPoll = {
      question: event.target.question.value,
      choices: [
        {  text: event.target.choice1.value, votes: 0 },
        {  text: event.target.choice2.value, votes: 0 },
        {  text: event.target.choice3.value, votes: 0 }
      ]
    };    
     
    // create the new poll
    Polls.insert(newPoll);
  }

});

Template.pollForm.events is how we attach events to this specific template. We are defining an event to handle the submit form event.

We can pull data from the form inputs using event.target.{input_name}.value.

After we have gotten all the data we need, we are going to insert the new poll into our database using Polls.insert() just like we did in our bootstrap.js file on the server-side of things. Go ahead and use your form to submit data and you’ll find that the new poll automatically gets added to the list of polls.

Adding Voting Capabilities

We’ve added polls to our overall template in app.js, handled processing the poll form in poll-form.js; now we will handle the last action, which is voting in the corresponding JS file, poll.js.

Inside of client/components/poll.js, let’s attach events to our template:


// attach events to our poll template
Template.poll.events({

  // event to handle clicking a choice
  'click .vote': function(event) {

    // prevent the default behavior
    event.preventDefault();
  
    // get the parent (poll) id
    var pollID = $(event.currentTarget).parent('.poll').data('id');
    var voteID = $(event.currentTarget).data('id');

    // create the incrementing object so we can add to the corresponding vote
    var voteString = 'choices.' + voteID + '.votes';
    var action = {};
    action[voteString] = 1;
    
    // increment the number of votes for this choice
    Polls.update(
      { _id: pollID }, 
      { $inc: action }
    );

  }

});

A fun thing we can do to traverse the DOM is to use jQuery to call the current event and then find exactly what we need. To update the vote number for what was just clicked, we will need to get the parent ID of the poll and then the index of what was just clicked.

We will be using MongoDB’s $inc operator to add 1 to the vote. Go ahead and click through your application and vote, and you’ll see the votes increment.

You may also be able to see other users voting in real time!

This is some very simple templating here with the built in Meteor features. For future larger projects, we’ll probably want to look at a routing solution like iron:router, currently the most popular Meteor package in Atmosphere, Meteor’s packages site.

Using Meteor Packages from Atmosphere

Let’s install Bootstrap to get some quick styling for our application. The thing to know about Meteor packages is that you only need to install them to get them to work.

Traditionally, to get Bootstrap, you would:

  • Download Bootstrap
  • Move Bootstrap into your project folders
  • Add Bootstrap using a <link> tag in your project’s <head>
  • Start using Bootstrap

In Meteor, all you have to do is install Bootstrap and it’s automatically applied to your project. Meteor’s build system will include the CSS on its own.

Let’s install Bootstrap found in Atmosphere.

meteor add twbs:bootstrap

meteor-add-bootstrap

Now, if we go look at our application, we’ll see that our styling is there!

bootstrap-styled

Note: You could also do things the old fashioned way of grabbing the Bootstrap file and adding it to your public/ folder and then linking to it in the document <head> if you want to go that route.

A Little More Simple CSS

In addition to having Bootstrap, let’s add some of our own styles real quick to app.css to get our grid of polls looking a bit better:

Let’s make our polls use flexbox to make it easier to create a grid of polls.

Inside of app.css, let’s add some quick styling:


body  {
  padding-top:50px; 
}
.polls  {
  display:flex;
  flex-flow:row wrap;
  justify-content:center;
}
.poll   {
  width:25%;
  margin:20px;
}

We now have a decent looking application with the basic functionality of a polling application!

bootstrap-custom-styled

Notes for Production

autopublish

Since our application is still in development mode, all collections are automatically published from our server and subscribed to on our client. We probably won’t want this behavior in our production applications since it doesn’t make sense for users of our application to subscribe to EVERY part of our application, even the parts they aren’t using at any one time.

It makes more sense to only have a user subscribed to the data that they are seeing at any one time. If you go look into .meteor/packages to see the packages in your Meteor application, you’ll see one called autopublish. This will need to be removed and then you’ll have to publish and subscribe to data manually in your application.

To remove this package, use:

meteor remove autopublish

Then you can use Meteor.publish() and Meteor.subscribe().

Deploying

Now we want the whole world to see our super cool new application. We can use Meteor’s servers to show off our application.

With one simple command, our entire app will be available online:

meteor deploy scotch.meteor.com

You can change out the subdomain for anything you like as long as it isn’t already taken. We’ll explore deploying to your own servers in future articles.

Conclusion

Meteor provides so many great tools to easily and quickly prototype your applications. Real-time, JavaScript client and server-side, the packages system, build system, minimongo, and so much more.

Hopefully this has given you a good taste of how quickly you can build an application in Meteor.

Chris Sevilleja

Co-founder of Scotch.io. Slapping the keyboard until something good happens.