Day 15: Meteor--Building a Web App From Scratch in Meteor

So far in this series we have looked at Bower, AngularJS, GruntJS, and PhoneGap JavaScript technologies. Today for my 30 day challenge, I decided to go back to JavaScript and learn a framework called Meteor. Although Meteor has a very good documentation, but it misses a beginner tutorial. I learn better from tutorials as they help me get started with a technology quickly. In this blog, we will learn how to build an epoll application using Meteor framework.

Meteor logo

What is Meteor?

Meteor is a next generation open source platform for building real-time web apps in minimum time. It has a very different philosophy compared to other existing JavaScript frameworks like AngularJS, BackboneJS, etc. Normally, when we work with backbone or angular, the client side(Angular or Backbone) talks with a REST backend. We can write REST backend in any technology like Java, NodeJS, PHP,etc.

In Meteor, DDP(Distributed Data Protocol) protocol is used to transfer the data between client and server. DDP is a standard way to solve the biggest problem facing client-side JavaScript developers: querying a server-side database, sending the results down to the client, and then pushing changes to the client whenever anything changes in the database.

The server side of a Meteor application works on top of Node and MongoDB. We write both the client and server side of the application using Meteor APIs. In future developers will have the choice to use any other database apart from MongoDB.

Why Meteor?

If you want to convince yourself why you should learn Meteor then read these seven core principles of Meteor.

Application Usecase

In this blog, we will develop an epoll application which allows users to post and vote on questions. The application can do the following :

  • When a user goes to '/' url of the application, then he will see a list of questions. User will have to sign in using Twitter to post a new question or vote on an existing question. In the image shown below, we can see that yes and no buttons are disabled as the user is not logged in.

Epoll Home Page

  • When a user clicks on Sign in with Twitter, then he has to authorize the epoll app to use his account. After successful authorization, user can view the page where he can post a new poll question or vote on an existing question.

Epoll After Signin

  • Finally, a user can post a new question or vote on an existing question.

Github Repository

The code for today's demo application is available on github: day15-epoll-meteor-demo.

Installing Meteor

It is very easy to get started with Meteor. If you’re using Mac or Linux, then open a Terminal and type the command shown below.

For windows user, please refer to the documentation.

Create a Meteor Application

Creating a Meteor app is very easy. Once we have installed Meteor, all we need to do is run the meteor create command.

$ meteor create epoll

It will create a directory named epoll on the file system and put some template files in it. The project structure is shown below.

Meteor Template Application Structures

Let us look at each of these one by one:

  1. The .meteor folder is for storing meteor specific files. It includes a .gitignore file to ignore local folder. The local folder stores the MongoDB database files and application build. The packages file inside .meteor specifies all the packages this application is using. You can think of them like npm packages. Meteor provides functionalities as packages. We will be using few packages later in the post. The release file mentions the meteor version. In this blog, we are using the latest version of meteor i.e. 0.6.6.3.

  2. The epoll.css is used to specify the application CSS styles.

  3. The epoll.html is the application HTML markup. The meteor framework currently uses handlebars as the default template engine. In the future Meteor might support other template engine as well. As per the Meteor documentation,

    Today, the only templating system that has been packaged for Meteor is Handlebars.

  4. The epoll.js is the heart of meteor application. The epoll.js Javascript file is deployed on both the server and the client. This allows developers to write a function once and use it on both the server and the client sides. The template epoll.js created by meteor is shown below.

if (Meteor.isClient) {
  Template.hello.greeting = function () {
    return "Welcome to epoll.";
  };
 
  Template.hello.events({
    'click input' : function () {
      // template data, if any, is available in 'this'
      if (typeof console !== 'undefined')
        console.log("You pressed the button");
    }
  });
}
if (Meteor.isServer) {
  Meteor.startup(function () {
    // code to run on server at startup
  });
}

In the code shown above, Meteor.isServer and Meteor.isClient flags are used to separate the server code from the client code.

To run the application, change the directory to epoll and then type meteor command.

$ cd epoll
$ meteor

The application will be running at http://localhost:3000. On button click, we will see a message in chrome developer tools You pressed the button.

Meteor Template Application

In the epoll.js, change the greeting to as shown below.

Template.hello.greeting = function () {
    return "The Missing Meteor Tutorial!!!";
  };

The change will be automatically applied and page will be reloaded.

Meteor Template Application

Where is MongoDB?

As mentioned before, Meteor uses MongoDB to store the data. When we install the meteor package, it also download the latest version of the MongoDB. We can see the MongoDB installation in the <user.home>/.meteor directory. It took me sometime to figure out where MongoDB is running. I used ps -ef command to find the location of MongoDB installation.

$ ps -ef|grep mongo
 
501  1704  1687   0  2:22PM ttys001    0:09.28 /Users/shekhargulati/.meteor/tools/0b2f28e18b/mongodb/bin/mongod --bind_ip 127.0.0.1 --smallfiles --nohttpinterface --port 3002 --dbpath /Users/shekhargulati/day15/epoll/.meteor/local/db

On my machine, MongoDB is running on port 3002. This avoids existing MongoDB installation which by default runs on 27017 port. The database directory points to the .meteor folder in the application directory.

Meteor Smart Packages

As mentioned before Meteor implements functionality as packages. These packages ork both in the browser and on the server. To view the list of all packages supported by Meteor run the command shown below.

$ meteor list

To add a new package use meteor add command and to remove a package use meteor remove command.

Add Twitter Bootstrap Package

We will use Twitter Bootstrap to style the UI. To add Bootstrap, type the following command.

$ meteor add bootstrap

The only caveat with Twitter Bootstrap package is that it is not the latest verion of Bootstrap. The version of Twitter Bootstrap supported by Meteor package is 2.3.2.

Add Twitter Authentication Package

In the epoll application we will use Twitter Authentication to secure the functionality. A user has to first authenticate with Twitter and then he can either vote on a question or add a new question.

Meteor provides accounts-ui package to add login widgets to an app. To add the accounts-ui package, run the following command.

$ meteor add accounts-ui

Now we will add authentication provider to the application. In this application, we are using Twitter but it can be facebook, github, google, weibo, or meetup as well

$ meteor add accounts-twitter

After adding the package, we have to update the epoll.html to show Twitter signin button. Update the epoll.html with the one shown below.

<head>
  <title>Epoll : Share your opinion online, anywhere, anytime</title>
 </head>
 
<body>
 
    <div class="navbar navbar-static-top navbar-inverse">
 
      <div class="navbar-inner">
        <div class="container">
          <a class="brand" href="/">Epoll</a>
          <ul class="nav pull-right">
            <li>
              {{loginButtons}}
            </li>
          </ul>
        </div>
      </div>
 
</div>
 
    <div class="container" id="main">
        {{> banner}}
    </div>
</body>
 
<template name="banner">
    <div class="container">
        <div class="row">
            <div class="span6">
                <div class="well">
                    <h4>Sign in using Twitter to submit new questions or to vote on existing questions.</h4>
                    {{loginButtons}}
                </div>
            </div>
        </div>
    </div>
</template>

Also add following style to epoll.css.

/* CSS declarations go here */
.login-display-name{color: white }
.login-button{background-color: white}
 #main {
    padding-top:20px;
}

The application will automatically update and you will see the page as shown below.

Epoll Twitter Authentication Configuration

Now click on Configure Twitter Login and we will be asked to enter the consumer key and consumer secret of the twitter application.

Twitter Authentication Configuration

To get the configuration information, we have to create a new twitter application and then save the configuration. After saving the configuration, we will be able to sign in using twitter.

Twitter Authentication Configuration

After authorizing the app to use our account, we will be logged into the application. We can sign out when we are done with the application.

Twitter Authentication Configuration

A new user will be created in the users collection in MongoDB. To view the user, we will connect to the MongoDB database server using the mongo client.

$ ~/.meteor/tools/0b2f28e18b/mongodb/bin/mongo --port 3002
 
MongoDB shell version: 2.4.6
connecting to: 127.0.0.1:3002/test
> show dbs
local   0.03125GB
meteor  0.0625GB
> use meteor
switched to db meteor
 
> show collections
meteor_accounts_loginServiceConfiguration
system.indexes
users
> db.meteor_accounts_loginServiceConfiguration.find()
{ "service" : "twitter", "consumerKey" : "xxx", "secret" : "xxx", "_id" : "xxx" }
> 
> 
> db.users.find().pretty()
{
    "createdAt" : ISODate("2013-11-11T18:03:23.488Z"),
    "_id" : "xx",
    "services" : {
        "twitter" : {
            "id" : "66993334",
            "screenName" : "shekhargulati",
            "accessToken" : "xxx-xxx",
            "accessTokenSecret" : "xxx",
            "profile_image_url" : "http://pbs.twimg.com/profile_images/378800000254412405/e4adcf8fb7800c3e5f8141c561cb57e4_normal.jpeg",
            "profile_image_url_https" : "https://pbs.twimg.com/profile_images/378800000254412405/e4adcf8fb7800c3e5f8141c561cb57e4_normal.jpeg",
            "lang" : "en"
        },
        "resume" : {
            "loginTokens" : [
                {
                    "token" : "xxx",
                    "when" : ISODate("2013-11-11T18:03:23.489Z")
                }
            ]
        }
    },
    "profile" : {
        "name" : "Shekhar Gulati"
    }
}
> 

Define Application Layout

The template application created by Meteor does not follow the best practices defined for Meteor application layout. The epoll.js file is shared by both client and server. Anybody can view the epoll.js using browser developer tools.

There are times where we don’t want to share everything between the client and the server. If we have some server specific code, we would not like Meteor to be sending that down to the client. In Meteor, we can use client and server directories to segregate code between the client and server. Create the client and server directories inside epoll folder.

$ cd epoll
$ mkdir client server

Now create epollclient.js under client folder and epollserver.js file under server folder.

$ touch client/epollclient.js
$ touch server/epollserver.js

Move the client code from epoll.js to client/epollclient.js as shown below.

Template.hello.greeting = function () {
    return "The Missing Meteor Tutorial!!!";
};
 
Template.hello.events({
    'click input' : function () {
      // template data, if any, is available in 'this'
      if (typeof console !== 'undefined')
        console.log("You pressed the button");
    }
});

Similarly, move the epoll.js server side code to server/epollserver.js.

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

Now delete the epoll.js file.

$ rm -f epoll.js

Remove insecure package

Every Meteor application has a special package called insecure installed. This package gives the client the ability to perform all the operations on the database. This should be removed from the application. The Meteor documentation also recommends removing it.

By default, a new Meteor app includes the autopublish and insecure packages, which together mimic the effect of each client having full read/write access to the server's database. These are useful prototyping tools, but typically not appropriate for production applications.

To remove the insecure package, type the command shown below.

$ meteor remove insecure

Ability to Add Questions

Now we will add the functionality which allows logged in users to submit new questions.

<head>
  <title>Epoll : Share your opinion online, anywhere, anytime</title>
</head>
 
<body>
 
  <div class="navbar navbar-static-top navbar-inverse">
 
      <div class="navbar-inner">
        <div class="container">
          <a class="brand" href="/">Epoll</a>
          <ul class="nav pull-right">
            <li>
              {{loginButtons}}
            </li>
          </ul>
        </div>
      </div>
 
</div>
 
  <div class="container" id="main">
    {{#if currentUser}}
      {{> addquestion}}
    {{/if}}
    {{#unless currentUser}}
      {{> banner}}
    {{/unless}}
    </div>
</body>
 
<template name="banner">
  <div class="container">
    <div class="row">
        <div class="span6">
            <div class="well">
              <h4>Sign in using Twitter to submit new questions or to vote on existing questions.</h4>
              {{loginButtons}}
            </div>
      </div>
    </div>
  </div>
</template>
<template name="addquestion">
 
  <textarea rows="3" class="input-xxlarge" name="questionText" id="questionText" placeholder="Add Your Question"></textarea>
  <br/>
  <input type="button" class="btn-info add-question" value="Add Question"/>
</template>

The above shown html will render addQuestion template only when user is logged in the application. If you sign out from the application, you will not see the textarea to add new question.

We have to update both the client and server side code to implement the functionality.

In the client/epollclient.js, add the following code.

Template.addquestion.events({
    'click input.add-question' : function(event){
        event.preventDefault();
        var questionText = document.getElementById("questionText").value;
        Meteor.call("addQuestion",questionText,function(error , questionId){
          console.log('added question with Id .. '+questionId);
        });
        document.getElementById("questionText").value = "";
 
    }
});

In the code shown above

  1. We first bind to the click event on input type with 'add-question' class.
  2. Then we prevent the default click event and get the question text from the dom.
  3. Next we call the Meteor server method addQuestion. The server should be responsible for doing any risky stuff with the data like inserting, updating, or deleting. The client never sees the implementation and doesn’t personally modify the data. The server does all the work.

Now we will add the code to server/epollserver.js. We will first define a new collection called Questions. Then we will operate on this collection. Meteor uses minimongo as the API interface. To view all the operations supported by minimongo, please refer to the Meteor.Collection documentation.

Questions = new Meteor.Collection("questions");
 
Meteor.startup(function () {
    // code to run on server at startup
});
 
Meteor.methods({
  addQuestion : function(questionText){
    console.log('Adding Question');
    var questionId = Questions.insert({
          'questionText' : questionText,
          'submittedOn': new Date(),
          'submittedBy' : Meteor.userId()
      });
    return questionId;
  }
});

Now go to the application user interface and submit a new question.

Epoll Add Question

We can also view the data in MongoDB.

> db.questions.find().pretty()
{
    "questionText" : "Is Sachin Tendulkar the greatest batsman of all time?",
    "submittedOn" : ISODate("2013-11-11T18:23:02.541Z"),
    "submittedBy" : "Jnu6oXoAZ2um57rZ8",
    "_id" : "nhqvgDcZqgZgLdDB7"
}

List All Questions

The next functionality we are going to implement is listing all the questions. A user should be able to view all the questions even without log in.

Add the following to main div

{{> questions}}

Next, add the questions template to epoll.html.

<template name="questions">
    <h2>All Questions</h2>
    {{#each items}}
        {{> question}}
     {{/each}}
</template>
 
<template name="question">
    <div>
        <p class="lead">
            {{questionText}}
            <a class="btn btn-small btn-success yes {{#unless currentUser}}disabled{{/unless}}" href="#"><i class="icon-thumbs-up"></i> Yes {{yes}}</a>
 
            <a class="btn btn-small btn-danger no {{#unless currentUser}}disabled{{/unless}}" href="#"><i class="icon-thumbs-down"></i> No {{no}}</a>
        </p>
    </div>
</template>

The above HTML code should be self explanatory. The only thing worth mentioning is the use of unless control structure in the question template. The unless control structure makes sure that if user is not logged in then the disabled css is used.

To get all the questions, we will use the Questions collection on the client side to fetch all the documents. In the client/epollclient.js add the following code.

Questions = new Meteor.Collection("questions");
 
Template.questions.items = function(){
    return Questions.find({},{sort:{'submittedOn':-1}});
};

Implement Voting

The last functionality that we have to implement in this application is to allow logged in users to vote. This does not require any change in the html file as we have already added all the templates.

Add the code shown below to client/epollclient.js

Template.question.events({
 
    'click': function () {
        Session.set("selected_question", this._id);
    },
 
    'click a.yes' : function (event) {
      event.preventDefault();
      if(Meteor.userId()){
        var questionId = Session.get('selected_question');
        console.log('updating yes count for questionId '+questionId);
        Meteor.call("incrementYesVotes",questionId);
 
      }
    },
 
    'click a.no': function(){
      event.preventDefault();
      if(Meteor.userId()){
        var questionId = Session.get('selected_question');
        console.log('updating no count for questionId '+questionId);
        Meteor.call("incrementNoVotes",questionId);
      }
    }
 });

The code shown above does the following :

  1. It binds to click event on the question template. Whenever we click any question, it will set the questionId in the session. Session provides a global object on the client that you can use to store an arbitrary set of key-value pairs.
  2. When user clicks on"Yes" anchor tag, then we will get the selected questionId from the session, and then call the incrementYesVotes method on the server. We also check that user should be logged in before he can cast his vote using Meteor.userId() function.
  3. When a user clicks on "No" anchor tag, we call the incrementNoVotes function the server.

Finally, we add incrementYesVotes and incrementNoVotes in the server/epollserver.js. It uses Meteor's collection update functionality to increment the counter.

incrementYesVotes : function(questionId){
    console.log(questionId);
    Questions.update(questionId,{$inc : {'yes':1}});
  },
 
incrementNoVotes : function(questionId){
    console.log(questionId);
    Questions.update(questionId,{$inc : {'no':1}});
}

So each time user clicks on yes or no, the count will get updated. You can play with the application by going to http://localhost:3000.

Deploy Meteor Applications

There are couple of alternatives to deploy Meteor applications. We can deploy the Meteor applications on the test servers provided by Meteor or we can deploy the application on OpenShift. To deploy the Meteor application on OpenShift, please refer to this blog by Ryan.

To deploy on Meteor test server, run the command shown below.

$ meteor deploy epoll

The app is running at http://epoll.meteor.com/

That's it for today. Keep giving feedback.

What's Next

Thank you SO MUCH for this tutorial. Really helps me understand how to get around Meteor. EBI (even better if...) the mongodb connection command is simply 'meteor mongo'. I found the given instructions a bit confusing. Thanks, Nathan Otto

Thank you for this tutorial!

I'm having trouble getting the Sign in with Twitter button working. I have configured the app I created on dev.twitter.com and saved the consumer key and consumer secret. When I try and sign in with Twitter chrome says that it could not connect to localhost:3000. Please help!

Thanks again -B

Question,

how do you prevent not logged in users to add comments through engineered HTTP requests? All authorization code I see is on the client side?

Actually, the code IS unsafe, it lacks check if user is logged in in epollserver.js, you can add questions without logging in

Thanks for this post! I'm amazed that all the tutorials associated with meteor are closed-source and pay-walled. Seems a bit strange for an open source tool.