An Introduction to Meteor.js



An Introduction to Meteor.js

3 3


intro-to-meteor

Reveal.js presentation for "Introduction to Meteor.js"

On Github kcharvey / intro-to-meteor

An Introduction to Meteor.js

Kevin Harvey | @kevinharvey

Agenda

Introductions Conceptual Overview Demo

Who is this guy?

  • Senior Systems Engineer at SmileCareClub
  • Building the web since 2004

Who are you guys?

1. Front end vs. back end 2. Used Meteor previously 3. Used Meteor in production

What is Meteor?

1. Instead of jumping right into the demo... 2. Significant departure from typcial frameworks

Full-stack web framework

  • Pure-Javascript
  • Same API on the front and back ends

What is it like?

1. Often compared to Angular... 2. Got a back end

How is it different?

Requests vs. Subscription

1. Traditional web applications rely on the request/response loop... 2. This is the key differentiator

Single-Page Apps

... unless you install a router package

The reason it's compared to Angular most often is it's great at single-page apps

Database Everywhere

  • MongoDB on the server
  • MiniMongo in the browser
  • Meteor keeps it all in sync

Reactivity

This is some serious hocus pocus

Client UI and database are updated first Changes sent to server Issues are resolved after the fact

Latency Compensation

Continuing the wizadry theme

Changes from the server are updated automatically Models are simulated locally

Framework + Package Manager

$ meteor create myapp
$ meteor add accounts-ui accounts-password
$ meteor
Not only is does meteor create and run you app for development, it's also it's own package manager

Development Happens Fast

Hardly any boilerplate Tons of stuff for free Context switching reduced

Demo

questionator.meteor.com

Installing Meteor

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

Starting a Project

$ meteor create the-questionator
$ cd the-questionator
$ meteor
[[[[[ ~/dev/the-questionator ]]]]]            

=> Started proxy.                             
=> Started MongoDB.                           
=> Started your app.                          

=> App running at: http://localhost:3000/
0e8d8bc41bb780659af222d55ae39941886464db meteor

Initial Project Structure

$ ls
.meteor
the-questionator.css
the-questionator.html
the-questionator.js

the-questionator.html

<head>
  <title>the-questionator</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>
The default templating language is called "Spacebars"

the-questionator.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
  });
}

Installing Bootstrap

$ meteor add twbs:bootstrap
6cb5e166cfb5d341a7c48d0518208e56c3f64581

Make Moar Prettier

$ the-questionator.html
...
<body>
  <div class="container-fluid" 
	style="padding-top:60px;">
    
    <nav role="navigation"
      class="navbar navbar-default navbar-fixed-top">
      <div class="container-fluid">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">
            The Questionator
          </a>
        </div>
      </div>
    </nav>

    {{> hello}}
  </div>
</body>
...
97284406042a434e45fb9fdb8961946b07c51507

Even Moar Prettier!

$ the-questionator.html
...
    <form role="form">
      <div class="form-group">
        <textarea id="question"
          class="form-control" rows="2">
        </textarea>
      </div>
      <div class="form-group">
        <input class="btn btn-default"
          type="submit" value="Ask away" />
      </div>
    </form>

    {{> hello}}
  </div>
</body>
...
a2b8364122a437d9128f933cee08054997e61cce

Template Inclusion

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

Extending the Template

$ the-questionator.html
...
    {{> questionsList}}
  </div>
</body>

<template name="questionsList">
  <div id="questions">
    {{#each questions}}
      <hr />
      <h6>{{text}} <small>Votes: {{votes}}</small></h6>
      <a class="btn btn-default btn-xs vote-up">up</a>
      <a class="btn btn-default btn-xs vote-down">down</a>
    {{/each}}    
  </div>
  <hr />
</template>
Notice the {{> questionsList}}, {{#each}}, and {{variable}} syntax. Handlebars-like 'name=' gives us acess to the template in JS Code update on next slide.

Hardcoded Data

$ the-questionator.js
if (Meteor.isClient){
  var questionsData = [
    {
      text: 'Why does the sun shine?',
      votes: 0
    },
    {
      text: 'If you were a hot dog, and you'
	   + 'were starving to death, would you eat yourself?',
      votes: 0
    },
    {
      text: 'What is the airspeed velocity'
	  	+ ' of an unladen swallow?',
      votes: 0
    }
  ];

  Template.questionsList.helpers({
    questions: questionsData
  });
}
98926d42151e43666c53349a2c8e124607e6883a

Creating a Collection

$ the-questionator.js
Questions = new Mongo.Collection('questions');
...
c48d697579cb52f7f065a4f00928f013878100ac

Available in Client and Server

Terminal

$ meteor mongo
MongoDB shell version: 2.4.9
connecting to: 127.0.0.1:3001/meteor
Welcome to the MongoDB shell.
meteor:PRIMARY> db.questions.find()

Browser Console

> Questions.find()
> LocalCollection.Cursor {collection: LocalCollection, sorter: null, _selectorId: undefined...}
'meteor' command starts MongoDB on port 3001 on your local machine

Update the Helper

$ the-questionator.js
Questions = new Mongo.Collection('questions');

if (Meteor.isClient){
  Template.questionsList.helpers({
    questions: Questions.find(),
  });
}
65798515ae94176ce4b42d3a1d9a5567dca6ba6e

Preload Data

$ the-questionator.js

if (Meteor.isServer) {
  if (Questions.find().count() === 0) {
    Questions.insert({
      text: 'Why does the sun shine?',
      votes: 0
    });

    Questions.insert({
      text: 'If you were a hot dog, and you were...',
      votes: 0 
    });

    Questions.insert({
      text: 'What is the airspeed velocity of...',
      votes: 0 
    });
  }
}
2c4870635460a0ef7eac5c4b20cd8176b01a83ce

Manipulating Data

Try editing the data in a different browser window.

Remove Autopublish

$ meteor remove autopublish
ee57256c6fcb62446499cd6aa3002593299c8ea0 autopublish makes it easier to start prototyping quickly NOT FOR PRODUCTION

Publishing Data

if (Meteor.isClient) {
  Meteor.subscribe('questions');
  ...
}

if (Meteor.isServer) {
  ...
  Meteor.publish('questions', function() {
    return Questions.find();
  });
}
864c77cad1cbb1d6e76713d6f3e6bed9be23e3d4 In reality, this allows for fine-grained control of what is sent to the client.

Sorting Results

Template.questionsList.helpers({
  questions: Questions.find({}, {sort: {votes: -1}}), 
});
f5c5a93bd00cee143138cbe6a04e98006f72626e

Event Handling

if (Meteor.isClient) {
...  
  Template.questionsList.events({
    'click .vote-up': function(e) {
      e.preventDefault();
      Meteor.call('upvote', this._id);
    },
    
    'click .vote-down': function(e) {
      e.preventDefault();
      Meteor.call('downvote', this._id);
    }
  });
}
Commit hash in 'methods' slide

Methods

Meteor.methods({
  upvote: function(questionId) {
    var question = Questions.findOne(questionId);
    Questions.update(
      questionId,
      { $set: { votes: question.votes + 1 }}
    );
  },
  
  downvote: function(questionId) {
    var question = Questions.findOne(questionId);
    Questions.update(
      questionId,
      { $set: { votes: question.votes - 1 }}
    );
  }
});
8529986895d27bf106efd4d48e7d83bb733689e0

Template Refactor

{{> questionForm}}
...
<template name="questionForm">
  <form role="form">
    <div class="form-group">
      <textarea id="question"
        class="form-control" rows="2"></textarea>
    </div>
    <div class="form-group">
      <input class="btn btn-primary"
        type="submit" value="Ask away" />
    </div>
  </form>
</template>
Git hash in 'rinse-and-repeat' slide

Rinse and Repeat

if (Meteor.isClient) {
...
  Template.questionForm.events({
    'submit form': function(e) {
      Questions.insert({
        'text': $(e.target).find('#question').val(),
        'votes': 0
      });
    }
  });
}
15a233ac6829932ae518a5a2b758010c2f8b7804

Accounts

$ meteor add accounts-twitter
$ meteor add ian:accounts-ui-bootstrap-3
{{> loginButtons}}

Adding {{> loginButtons}}

<div class="navbar-header">
  <a class="navbar-brand" href="#">The Questionator</a>
  
  <button type="button" class="navbar-toggle collapsed" 
    data-toggle="collapse" data-target="#navigation">
    <span class="sr-only">Toggle navigation</span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
  </button>
</div>

<div class="collapse navbar-collapse" id="navigation">
  <ul class="nav navbar-nav navbar-right">
    {{> loginButtons}}
  </ul> 
</div>

Configure Twitter Login

4802b03561e151b8bacaefd1d69a0b5f89b7aab1 API Key: OaAtzi0jcKhHJ5Pse8K2KH46U API Secret: hfNvcGLIsZA63lqo5Olhr48BdyfHeSQSJbNQ9X24PDJdXa1aZu

Restricting Access

Questions.allow({
  insert: function(userId, doc) {
    return !! userId;
  }
});

// .. and the methods we added earlier
274a59fb580985126775a3892c05955a7c240f68

Removing "Insecure"

$ meteor remove insecure
62406bc2f2f5277f86a9a4a4ec3957411c4d38d7

End of Demo

Deployment

This is a little beyond the scope of this talk, but just to give you a few ideas...

Deploy to meteor.com

$ meteor deploy questionator.meteor.com
Deploying to http://questionator.meteor.com
That's how I deployed the sample app for this talk. Will ask you for an email address, and then just work.

Modulus

$ npm install -g modulus
$ modulus login
$ modulus project create
PaaS privder specializing in Node.js apps. Open sourced 'demeteorizer' for turning metoer apps into straight up Node.js

Meteor Up

$ npm install -g mup
$ mkdir ~/deploy-the-questionator
$ cd ~/deploy-the-questionator
$ mup init
Install to your own AWS, Digital Ocean, etc.

Behind the curtain...

A few random topics to help take some of the magic out all this

Web Sockets

A departure from the request/response loop

https://developer.mozilla.org/en-US/docs/WebSockets Opens a socket between a server and the client browser.

Distributed Data Protocol (DDP)

Publication & Subscription

http://2012.realtimeconf.com/video/matt-debergalis Meteor-specific protocol for managing publications and subscriptions over a web socket

Oplog Tailing

Getting realtime updates from MongoDB

https://github.com/meteor/meteor/wiki/Oplog-Observe-Driver How Meteor gets realtime updates from MongoDB

Tracker

Hot code pushes, reactions to database changes, etc.

https://github.com/meteor/meteor/tree/devel/packages/tracker Keeps track of dependent computations Previously known as 'Deps'

Resources

Fin

Thanks

Kevin Harvey | @kevinharvey