Learn Meteor Fundamentals and Best Practices
July 25, 2012
This tutorial is available in:
Spanish thanks to Miquel serra caldentey
French thanks to Etienne Balit
Introduction
Want to learn how this new, fandangled Meteor thing works? Great, you’re in the right spot. I want to show you around a Meteor project and give you some best practice tips for you to keep in mind when you want to make your own Meteor application.
What is Meteor?
Meteor lets you create extraordinarily dynamic pages with frighteningly little code. Keep in mind that Meteor is super-beta. Version 0.5.7
at the time of this writing. Don’t be surprised if it doesn’t work perfectly!
It’s written in Javascript on Node.js, and consequently most of the code that you will be writing will also be Javascript. Not too much of a surprise there. If you want to brush up on your Javascript, take a look at Javascript Garden, it’s an excellent resource.
For storing data, Meteor uses MongoDB. Despite using MongoDB, Meteor actually uses minimongo as the interface. It’s fairly full featured but doesn’t exactly have all of the methods that the standard MongoDB interface has. You don’t need to actually know how MongoDB works exactly, but I would recommend taking a look at Meteor’s docs for Collections to get a good idea of what you’re in for.
Meteor currently uses handlebars for the template engine. Right now, that’s the only choice you have, though supposedly in the future the ability to drop in a different template engine will become available.
At the end of the day, you’re still making websites so you’re still going to need to know some HTML and CSS as well.
The Basics
A Meteor project consists of mostly just Javascript files. If you place a *.js
file anywhere in your project, Meteor will automatically load it up and run it. Every Javascript file you write in a Meteor project is deployed to both the server and the client (well not exactly, as you’ll see in a moment). This is one of the things that makes Meteor really, really cool: since everything you’re writing is Javascript, you can write a function once and use it for both the server and the client!
It actually gets cooler than that. You can also do other “magical” things like put *.less
files anywhere in your project, and they will automatically be compiled and sent to the client and included on the page.
There are times that you want to separate your server code from your client code. Thankfully, Meteor has a couple flags to help you out: Meteor.isServer
and Meteor.isClient
.
In the example below, the Javascript console in the browser will log “Hi. I’m CLIENT”, and the Meteor output on the server will print “Hi. I’m SERVER”.
// This function is available on both the client and the server.
var greet = function(name) {
console.log("Hi. I'm " + name);
}
// Everything in here is only run on the server.
if(Meteor.isServer) {
greet("SERVER");
}
// Everything in here is only run on the client.
if(Meteor.isClient) {
greet("CLIENT");
}
It’s really simple. Code sharing between the client and server makes it so that you can maximize code reuse, which ideally reduces development time.
Project Structure
There are times where you don’t want to share everything between the client and the server, though. If you have some private algorithms executing on your server, you don’t want Meteor to be sending that down to the client for the world to see. Meteor provides two “special” directories that will help segregate code between the client and the server: [project_root]/client/
and [project_root]/server/
. Javascript in the server directory will not be sent to the client and will only be executed on the server. Code in the client directory will only be run on the client. This is nice because it relieves us of having to use Meteor.isClient
and Meteor.isServer
all over the place. Instead, just put your client code in the client directory and leave it at that!
The project structure is important when considering which files get loaded before others. Lets say that you have two files and one depends on the other having loaded first. How do you know what order your Javascript files are getting loaded in? Here are the rules:
- Files in
[project_root]/lib
are loaded first. Obviously, put libraries in this directory. - Files are sorted by directory depth. Deeper files are loaded first.
- Files are sorted in alphabetical order.
main.*
files are loaded last. These are nice for code that needs to be run after every other script and library has loaded.
Meteor has some special directories that help you deal with breaking apart client/server code and deal with load order:
[project_root]/lib/
- Files in this directory will get loaded before your client/server code starts.[project_root]/client/
- Files here are only sent to the client’s browser and aren’t available or run from the server.[project_root]/server/
- Files here are only run on the server and aren’t available on the client.[project_root]/public/
- Meteor serves static media from this directory. If you put image.jpg in here, feel free to refer to it directly as image.jpg in your html.[project_root]/.meteor/
- Meteor keeps special, project related info in here, like which modules you’ve installed. You really shouldn’t need to poke around in here.
Reactivity
Meteor saves you the hassle of manually replacing portions of your page when the data changes. It does this by having “reactive” data sources and contexts. A reactive context is just a function that will get re-run if it contains a reactive data source that gets updated. This is a little hard to wrap your mind around initially, so the following example should clear things up.
Below we have the html page and a Meteor Template called cool_dude
, and a function in the client Javascript which will give a value of username
for the template to render.
<html>
<head>
</head>
<body>
|{|> cool_dude |}|
</body>
</html>
<template name="cool_dude">
<p class="important">|{| username |}| sure is one cool dude!</p>
</template>
// On the client:
Template.cool_dude.username = function() {
return "Andrew Scala";
};
When the page renders, it will say “Andrew Scala sure is one cool dude!” (which is true).
Templates are reactive contexts: if it depends on a reactive data source to render, then it will re-render itself whenever that data source changes. The client-side Session
is a reactive data source. It will store information like a key-value pair on the client only, and it will be erased when the page is refreshed.
Lets change the template context to use a reactive data source:
// When the app starts,
// associate the key "username" with the string "Andrew Scala"
Meteor.startup(function() {
Session.set("username", "Andrew Scala");
});
Template.cool_dude.name = function() {
return Session.get("username");
};
The template now is pulling the value username
from the Session value "username"
. We now have a reactive data source in a reactive context. If the value stored in Session for "username"
ever changes, the template will automatically get re-rendered on the page with the new value. Lets set a new value for "username"
:
Session.set("username", "Bill Murray");
Immediately after this call, regardless of where it is in the code, the page will now say “Bill Murray sure is one cool dude!” (which is also true).
I’d list the other reactive contexts and data sources here, but you can read about them for yourself in Meteor’s Reactivity Docs.
Publish/Subscribe
The server publishes data for the client to use, and the client subscribes to the server’s published data. It’s difficult to understand the relationship between the server’s published data and the client’s subscription at first. The rule of thumb is this: The client should only be able to access data that it requires to operate at the current point in time, nothing more. For example, if you have a chat application, the client should not be receiving messages from every chat channel on your website, but only the messages in the channel that the user is visiting. Nor should it know about users inside other channels.
Here’s an example of poorly created publish/subscription. The client can see every message in the database:
var Messages = new Meteor.Collection("messages");
if(Meteor.isServer) {
Meteor.publish("messages", function() {
return Messages.find({});
});
}
if(Meteor.isClient) {
Meteor.subscribe("messages");
}
The client can now do Messages.find({})
and have access to every message in the database. Bad.
We can fix this by specifying a parameter during the subscription which narrows down all the messages to something that the client actually needs (the messages in the channel "cool_people_channel"
):
var Messages = new Meteor.Collection("messages");
if(Meteor.isServer) {
Meteor.publish("messages", function(channel_name) {
return Messages.find({channel: channel_name});
});
}
if(Meteor.isClient) {
Meteor.subscribe("messages", "cool_people_channel");
}
Now, when the client connects and does a Messages.find({})
to get a list of messages, it only gets the ones listed in the "cool_people_channel"
channel.
Finally, lets say the chat channel is something that can and will change. We don’t want to be locked into only seeing the messages that exist in "cool_people_channel"
. Taking our newfound knowledge of Meteor’s “reactivity”, we can create a dynamic subscription off of a session variable:
var Messages = new Meteor.Collection("messages");
if(Meteor.isServer) {
Meteor.publish("messages", function(channel_name) {
return Messages.find({channel: channel_name});
});
}
if(Meteor.isClient) {
Session.set("current_channel", "cool_people_channel");
Meteor.autorun(function() {
Meteor.subscribe("messages", Session.get("current_channel"));
});
}
Meteor.autorun is a reactive context, meaning everything inside will get re-run if a reactive data source changes inside. We’re storing the channel we’re in inside the Session under "current_channel"
. If that session value changes, then the subscription is renewed and we have access to different messages! If the user wanted to join a the channel “breakfast talk”, we would run Session.set("current_channel", "breakfast_talk")
, which would trigger the autorun, and give us access to messages in the “breakfast_talk” channel only.
There are times where you may want to publish the entire collection to the client. Think carefully about what the client actually needs. It may be wise to only send certain fields from that collection rather than entire documents.
Server Methods
Since the client shouldn’t be allowed to do anything other than look at what’s in the database, you’re surely wondering how to get the client to actually store information. The solution is to use Meteor’s server methods. The idea is that you define all the functions on the server that do dangerous stuff like modify and update data, and then let the client call those functions and get return values like regular functions. The client never sees the implementation and doesn’t personally modify the data. The server does all the work.
To add a user to your database, lets suppose there’s a method called create_user
that takes a username and lets the server do the inserting. It’ll give the client an ObjectID back so it can fetch user info and do whatever it wants with it later.
if(Meteor.isServer) {
Meteor.methods({
create_user: function(username) {
console.log("CREATING USER");
var USER_id = Users.insert({name: username});
return user_id;
},
});
}
// Remember, the client's browser only ever sees the code below:
if(Meteor.isClient) {
var username = "Andrew Scala";
Meteor.call("create_user", username, function(error, user_id) {
Session.set("user_id", user_id);
});
}
In this example, my user_id gets set into the client’s Session, and any templates that use my user_id will be updated right away.
Thanks
Ready to build a real Meteor application now? Stay tuned, part 2 is coming along which will walk you through making an entire app!
Update 1/30/13 I know I said there would be a part 2 coming along, but it’s going to take a significant amount of time to make and I simply don’t have time for that right now.
Hopefully in the future I’ll get around to it, but I can’t promise it anymore.
Update 3/8/13 Updating tutorial to reflect updates to Meteor thanks to @tmeasday
If you’ve found this useful, be sure to let me know! I really appreciate it.
Cheers,
Andrew Scala
email twitter post archive comments powered by Disqus