Rapid Development with Meteor

by Michael Brook, CTO of Pitchly

@michaelcbrook

What is Meteor?

Meteor is a reactive full-stack JavaScript framework that allows you to get shit done quickly

Traditional stack

Data layer

Data access layer

API layer

Model

View

Frontend

Backend

Meteor stack

Data layer

Thin permission layer

View

Frontend

Backend

What's it made of?

Frontend:

Blaze

Meteor's template rendering engine

Tracker

Meteor's reactive system

Backend:

Publications

"Get" data from the server

Methods

"Post" data to the server

MongoDB

The underlying database

What does it mean to be reactive?

Reactive programming is a development philosophy where instead of telling the computer how to do something, you tell it what to do.

When the data changes, update the view that depends on that data automatically

Alt:

chat.pitchly.net

Old model

client

Event: send message

Event: toggle hint box

AJAX call

API endpoint

Data access layer

Find DOM element

$('.my-element')

Set HTML

$el.html(response)

<div>Message</div>

Event 2

Event 3

Event 4

Wait, but the user logged out.

 

Wut?

I want my app to be real time!

3

  • Still a delay
  • A lot of wasted bandwidth
  • Unnecessary DOM manipulations

Meteor's Model

1

0

1

1

0

0

1

0

1

0

1

0

1

1

0

0

1

1

1

0

1

Subscribe: Only give me last 100 messages

Publish: Here are the last 100 messages

Helper reacts to data or UI change

client

1

{{#each messages}}

    <div class="message">{{message}}</div>

{{/each}}

  • Message 1
  • Message 2
  • Message 3

Event: send message

Method validates

& updates

UI state

Event: toggle hint box

Pub/Sub

The server publishes a refined data set.

The client subscribes to that data set and continues to receive updates for the duration of the session.

Meteor.publish("messages", function() {
  return Messages.find({}, {
    fields: { name: 1, message: 1, createdAt: 1, announcement: 1 },
    limit: 100,
    sort: { createdAt: -1 }
  });
});
Meteor.subscribe("messages");

Client

Server

Meteor.publish("messages", function() {
  if (!this.userId) {
    this.ready();
    return;
  }
  return Messages.find({ ownerId: this.userId }, {
    fields: { name: 1, message: 1, createdAt: 1, announcement: 1 },
    limit: 100,
    sort: { createdAt: -1 }
  });
});

Server (restricted to logged-in user)

Methods

Methods validate input and can commit to the database

Meteor.call("sendMessage", { message }, (error, response) => {
  if (error) alert(error.reason);
});

Client

Meteor.methods({
  'sendMessage'(data) {
    check(data, {
      message: String
    });
    if (data.message=="") {
      throw new Meteor.Error("message-empty", "Your message is empty");
    }
    Messages.insert({
      message: data.message,
      createdAt: new Date()
    });
  }
});

Server

Meteor.methods({
  'sendMessage'(data) {
    if (!Meteor.userId()) {
      throw new Meteor.Error("logged-out", "You must be logged in.");
    }
    check(data, {
      message: String
    });
    if (data.message=="") {
      throw new Meteor.Error("message-empty", "Your message is empty");
    }
    Messages.insert({
      ownerId: Meteor.userId(),
      message: data.message,
      createdAt: new Date()
    });
  }
});

Server (validating logged-in user)

Templates

Templates contain helpers, events & other templates

JS

<body>
  {{#each messages}}
    <div class="message {{#if announcement}}announcement{{/if}}">
      {{message}}{{#if announcement}} <img src="/tada.png" class="emoji" alt="tada">{{/if}}
    </div>
  {{/each}}
  <form>
    <input type="text" placeholder="Type your message here..."><button type="submit">Send</button>
  </form>
</body>

HTML

Template.body.onCreated(function bodyOnCreated() {
  this.subscribe("messages");
});
Template.body.helpers({
  messages() {
    return Messages.find({}, { sort: { createdAt: 1 } });
  }
});
Template.body.events({
  'submit form'(event, instance) {
    event.preventDefault();
    const $input = $(event.currentTarget).find('input');
    Meteor.call("sendMessage", { message: $input.val() }, (error, response) => {
      if (error) {
        alert(error.reason);
      } else {
        $input.val(""); //empty input on success
      }
    });
  }
});

More on reactivity

Helpers are functions that automatically re-run whenever a reactive data source inside the function changes.

A reactive data source can be data from the database, UI Session variables, or ReactiveVars attached to the template

The return value of a helper is generally rendered into a template using {{...}} syntax.

But wait, there's less...

Minimum Viable Code - Live Chat in < 20 lines

<body>
  {{#each messages}}<div>{{message}}</div>{{/each}}
  <form><input type="text"><button type="submit">Send</button></form>
</body>

Client HTML

import { Template } from 'meteor/templating';
import { Mongo } from 'meteor/mongo';
import './main.html';
const Messages = new Mongo.Collection('messages');
Template.body.helpers({
  messages() { return Messages.find({}, { sort: { createdAt: 1 } }); }
});
Template.body.events({
  'submit form'(event, instance) {
    event.preventDefault();
    const $input = $(event.currentTarget).find('input');
    Messages.insert({ message: $input.val(), createdAt: new Date() });
    $input.val("");
  }
});

Client JS

But where's the backend?

Wait, is that Mongo?!

Minimongo

Minimongo was developed by the people at Meteor to have identical syntax and function as MongoDB but on the browser.

Data loaded from the server via pub/sub is automatically inserted into Minimongo and you can query it as you would the real database.

Minimongo is a reactive data source.

(You don't even have to use it with a real database)

Running in prototype mode

For non-critical apps, you can create an app that saves and retrieves data to/from a MongoDB database without creating any backend.

The point of pub/sub is to create the security needed by production apps.

Optimistic UI

When Method code is shared between the backend and frontend, Meteor can predict the result of the Method immediately and render it to the client.

In case of error, it will undo the change.

The result is zero latency.

(Using this, backend code may be visible to the client but it can't be run by the client.)

Mongo Cursors

members() {

    return Members.find({ groupId: "1234" });

}

Mongo cursors can be iterated on directly and their properties get passed on.

{{#each members}}

    <div class="name">{{name}}</div>

    <div class="employeeId">{{employeeId}}</div>

{{/each}}

helper

Local State

You don't always need to save state in the DB. Often times, you want a UI element to "toggle" between states, saved locally.

this.activeTab = new ReactiveVar("shoes")

activeTab() {

    return Template.instance().activeTab.get();

}

helper

'click .tab'(event, instance) {

    instance.activeTab.set(this.tab);

}

event

<div class="tab {{#if equals activeTab "shoes"}}active{{/if}}">Shoes</div>

<div class="tab {{#if equals activeTab "pants"}}active{{/if}}">Pants</div>

equals (var1, var2) {

    return (var1===var2);

}

helper

Sessions

Sessions behave similarly to ReactiveVar, except they are globally available for as long as the user is on the site.

Sessions can also hold multiple properties.

Session.set("layout", "grid")

Session.get("layout")

(Session can survive a hot load push)

Hot Loading

Oh yeah, another cool thing about Meteor is anytime there is a change to the app, the changes push to all clients right away.

Code Distribution

How does Meteor know which code is on the frontend and which is on the backend?

Meteor is very opinionated...

Option 1

if (Meteor.isClient) { ... }

if (Meteor.isServer) { ... }

Option 2

client

server

* recommended

Accounts

Meteor handles account management for you. The 'users' collection holds all user data, and Meteor takes care of everything from password encryption to "forgot password" workflows to signup emails. You can build or not build the UI yourself.

Account packages can be installed to support login for:

Mobile Apps?

You can make any Meteor app a mobile app. Meteor integrates with Cordova.

meteor add-platform ios

meteor add-platform android

Desktop also supported via a third-party package, using Electron

Everything in one code base

Common Questions

Why should I use Meteor over React or Vue?

Meteor is a full-stack framework whereas React and Vue are frontend frameworks.

Meteor is merely a collection of many different toolsets combined into one, as such each component is interchangeable.

React and Vue are used in Meteor very often. More so nowadays.

Do I have to use MongoDB?

No. It used to be you didn't have a choice. Nowadays, you can use other data stores via GraphQL and Apollo.

GraphQL was developed by Facebook. Apollo was created by the Meteor team and is the #1 GraphQL frontend client.

Very recently, Apollo and GraphQL have added support for pub/sub, for the first time creating a "real-time" API.

https://medium.com/@michaelcbrook/how-to-get-apollo-2-0-working-with-graphql-subscriptions-321388be030c

Is it scalable?

"Premature scaling is the number one cause of startup death. You shouldn’t spend time optimizing a product when you don’t even know if users want it."

- Startup Genome

"It was certainly brave to build an app for three platforms in a little over three months with hopes to reach thousands. Meteor not only made it possible to build the app in that time frame, but Meteor was able to scale wonderfully."

- David Woody, Game Developer

How do you deploy it?

Meteor Galaxy

MDG's paid hosted solution

MUP

http://meteor-up.com/

(deploy anywhere)

  • auto-scaling
  • no dev-ops
  • on AWS
  • $30/mo.

I deployed to AWS with MUP in under 45 mins.

$8/mo.

Recap

Out of the box, Meteor does some pretty awesome things

  • Write significantly less code in less time
  • Automatically get real-time everywhere
  • Any component of the stack is interchangeable
  • Get account management free
  • Easily deploy as mobile apps
  • Single code base in one language
  • Re-use code between frontend and backend
  • Mongo on the client
  • Plug into the NPM community of packages
  • Supported by MDG, makers of Apollo
  • Easy to learn

Questions?

If you're interested in learning Meteor or JavaScript, I'm interested in teaching.

Register: http://iowacodeschool.com/

We've built an app platform that helps companies manage & action their data. (it's actually pretty sweet)

We're hiring! https://pitchly.net/

My name is Michael C. Brook

michael@pitchly.net

https://medium.com/@michaelcbrook

chat.pitchly.net

Rapid Development with Meteor

By Michael Brook

Rapid Development with Meteor

  • 1,263