Tutorial

Learn Meteor.js From Scratch: Build a Polling App

Updated on September 15, 2020
    Default avatar

    By Chris on Code

    Learn Meteor.js From Scratch: Build a Polling App

    This tutorial is out of date and no longer maintained.

    Introduction

    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 the 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.

    • 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, real-time 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 package 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.

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

    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

    Let’s create our first Meteor app!

    Setting Up the Project

    Install Meteor

    Follow the installation steps depending on your environment: OSX/Linux or Windows.

    Test to make sure everything is installed:

    1. 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:

    1. meteor create --example todos

    Example mobile application:

    1. 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.

    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:

    1. meteor create polling

    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:

    1. meteor

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

    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!

    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/example.jpg, then you can reference that file from HTML using <img src="images/example.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 define Meteor.isClient or Meteor.isServer anymore. If a file is in that folder, then Meteor knows which side of our application it belongs to.

    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: What is MongoDB?. 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:

    collections/polls.js
    Polls = new Mongo.Collection('polls');
    

    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 the 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:

    server/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.

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

    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 run 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!

    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.

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

    client/app.head.html
        <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.

    This will store the form for creating polls:

    client/components/poll-form.html
        <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.

    client/components/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.

    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.

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

    client/components/poll.html
        <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:

    client/app.js
        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:

    client/app.js
        // 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 the polls that we created in our bootstrap.js file showing in our application.

    Let’s wire up our form now to handle the 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:

    client/components/poll.js
        // 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.

    1. meteor add twbs:bootstrap

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

    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:

    client/app.css
        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!

    Notes for Production

    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:

    1. 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:

    1. meteor deploy example.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.

    Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

    Learn more about us


    About the authors
    Default avatar
    Chris on Code

    author

    Still looking for an answer?

    Ask a questionSearch for more help

    Was this helpful?
     
    Leave a comment
    

    This textbox defaults to using Markdown to format your answer.

    You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

    Try DigitalOcean for free

    Click below to sign up and get $200 of credit to try our products over 60 days!

    Sign up

    Join the Tech Talk
    Success! Thank you! Please check your email for further details.

    Please complete your information!

    Get our biweekly newsletter

    Sign up for Infrastructure as a Newsletter.

    Hollie's Hub for Good

    Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

    Become a contributor

    Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

    Welcome to the developer cloud

    DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

    Learn more
    DigitalOcean Cloud Control Panel