TL;DR: In this tutorial, I'll show you how easy it is to build a real-time web application with Meteor. Check out the repo to get the code.
Meteor is a full-stack JavaScript platform for developing modern web and mobile applications. Meteor provides a suite of technologies for building connected-client reactive applications, APIs, and a curated set of packages from the Node.js and general JavaScript community. It allows you develop in just one language, JavaScript, in all environments: server, web, and mobile.
Meteor is a project backed by the Meteor Development Group company. They are friends of the open source community. The MDG group also manages Apollo, the flexible production ready GraphQL client for React and Native apps. Meteor as a JavaScript platform has built a community around it over the years. Currently, there is a discussion forum, Stack Overflow channel, and Atmosphere - a repository of community packages. In addition, there is a community-curated list of meteor packages and resources on GitHub known as Awesome Meteor.
There are several websites and applications that run on Meteor. A few of them are Favro - a collaboration app, Reaction commerce - OSS platform for e-commerce sites, Oculus Health and Game Raven - An online gaming community.
Meteor Features
Meteor provides a lot out of the box. It ships with many features that make it worth considering when looking for a framework for your next project.
- Authentication: Meteor ships with session management and authentication features out of the box.
- Real-time Feature: Meteor is built from the ground up on the Distributed Data Protocol (DDP) to allow data transfer in both directions. In Meteor, you create publication endpoints that can push data from server to client.
- Routing: Meteor provides a
flow-router
package that allows client-side routing. - Custom Templating Engines: Meteor ships with its own templating engine but allows you to use other view libraries.
- Packaging for Mobile: Meteor allows you to easily package your web app into Android and iOS apps. With meteor, you can build for mobile.
- Galaxy: The Meteor Development Group (MDG) provides a service to run Meteor apps. Galaxy is a distributed system that runs on Amazon AWS. It saves you a lot of trouble in configuring and deploying your app to production.
Meteor Key Requirements
In order to use Meteor, you need to have the following tools installed on your machine.
- If you are operating on a windows machine, you need to have Chocolatey installed.
- If you are operating on an OS X or Linux machine, you do not need to have any special tool installed. Your terminal should be able to make a
curl
request. - iOS development requires the latest Xcode.
- MongoDB: Navigate to the mongodb website and install the MongoDB community server edition. If you are using a Mac, I'll recommend following this instruction. To avoid micromanaging from the terminal, I'll also recommend installing a MongoDB GUI, Robo 3T, formerly known as RoboMongo. You can then run
mongod
from the terminal to start up the MongoDB service on your machine.
Understanding Key Concepts in Meteor
Meteor uses the Publish-subscribe model. Check out this excellent article on how publications and data loading works in Meteor. In a typical framework architecture, there exists a separation of concern of functionalities; presentation, business, and data access realm.
"Meteor uses the Publish and subscribe model."
Tweet This
Data Layer: This is the data access layer. The data layer is typically stored in MongoDB.
View Layer: In a typical framework, the view simply presents data to the screen. In Meteor, there are template files. These files contains the view logic that accesses the Mongo Schemas. The view logic is typically placed in the
client/imports/ui
directory.Business Logic Layer: In Meteor, the
client
andserver
directories exist. The business logic is typically placed in theclient/imports/api
directory. However, any sensitive code that you don’t want to be served to the client, such as code containing passwords or authentication mechanisms, should be kept in theserver/
directory.
Build a Real-time Web App With Meteor
In this tutorial, we'll build a simple application called Slang Bucket. This app will allow users to add all sorts of slangs with their respective meanings. The Slang Bucket is a mini version of Urban Dictionary. Users will be able to add and delete slangs from the bucket.
Install Meteor and Scaffold Slang Bucket
Linux and Mac users can run the following command in the terminal to install Meteor:
curl https://install.meteor.com/ | sh
Windows users can install Meteor like so:
choco install meteor
The command above will install the latest version of Meteor. At the time of this writing, Meteor's latest version is 1.6.
Next, go ahead and create the Slang Bucket app like so:
meteor create slangbucket
The command above will create a new slangbucket
directory with some boilerplate files.
Run the following command to get the app up and running in the browser:
cd slangbucket
meteor
Open the URL, http://localhost:3000
, in the web browser to see the app running.
Slang Bucket: Default page
Directory Structure
Open up the slangbucket
code repository in an editor. These are the files that were created when you ran the command to scaffold the app.
- client
- main.js # a JavaScript entry point loaded on the client
- main.html # an HTML file that defines view templates
- main.css # a CSS file to define your app's styles
- server
- main.js # a JavaScript entry point loaded on the server
- package.json # a control file for installing NPM packages
- .meteor # internal Meteor files
- .gitignore # a control file for git
Select Templating Engine
Meteor ships with a templating engine called Blaze
. Blaze renders responses from HTML files and has a very familiar expression language. It uses double braces, {{ }}, and {{> }}.
- {{> }} - Used to include Meteor templates in HTML files
- {{ }} - Used to display data and logic from JavaScript files in the view(HTML) files.
Meteor is very configurable. You can use Angular and React with Meteor. If you want to use React as the view library, all you need to do is add react:
meteor npm install --save react react-dom
And configure it like so:
client/main.html
<head>
<title>Todo List</title>
</head>
<body>
<div id="render-target"></div>
</body>
client/main.jsx
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import App from '../imports/ui/App.jsx';
Meteor.startup(() => {
render(<App />, document.getElementById('render-target'));
});
Your UI elements can now be written with JSX.
If you want to use Angular, all you need to do is remove blaze
:
meteor remove blaze-html-templates
And replace it with the UI package for Angular:
meteor add angular-templates
Furthermore, install angular
and angular-meteor
:
meteor npm install --save angular angular-meteor
Go ahead and configure your templates like so:
client/main.html
<head>
<title>Todo List</title>
</head>
<body>
<div class="container" ng-app="slang-bucket">
</div>
</body>
client/main.js
import angular from 'angular';
import angularMeteor from 'angular-meteor';
angular.module('simple-todos', [
angularMeteor
]);
In this tutorial, we'll use the default Meteor templating engine, Blaze.
Create Slang Bucket Views and Display Static Data
First, add bootstrap by running the command below:
meteor add twbs:bootstrap
Create a new directory, imports in the slangbucket project folder. Inside the imports directory, create a ui folder.
Go ahead and create the following files:
ui/body.html
<body>
<div class="container">
<header>
<h1 class="text-center">Slang Bucket</h1>
</header>
<div class="col-sm-12">
{{#each slangs}}
{{> slang}}
{{/each}}
</div>
</div>
</body>
<template name="slang">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"><span class="btn">{{ slang }}</span></h3>
</div>
<div class="panel-body">
<p> {{ definition }} </p>
</div>
</div>
</template>
ui/body.js
import { Template } from 'meteor/templating';
import './body.html';
Template.body.helpers({
slangs: [
{ slang: "Yoruba Demon",
definition: "Nigerian guy (yoruba) who goes after a young lady's heart with no intention of loving her. They are typically met at parties, and would mostly wear white agbada."
},
{ slang: "Bye Felicia",
definition: "The perfect dismissal phrase for waving goodbye to someone or something unimportant."
},
{ slang: "GOAT",
definition: "An acronym for praising someone doing well in a certain activity. 'Greatest of all time'."
},
{ slang: "Low key",
definition: "Keeping some activity or news under wraps."
},
],
});
Head over to client/main.js
. Remove everything there and replace with:
import '../imports/ui/body.js';
Right now, your web app should look like this:
Meteor: Static Data
What's happening in the code above?
The client/main.js
loads up the body.js
file. In the body.js
file, we have a couple of things going on. The body.html
file is been imported. You can pass data into templates from JavaScript code using the Template.body.helpers
. Here, we defined a slangs
helper that returns an array of objects.
In body.html
, we invoked the data returned from the slangs
helper with the code below:
{{#each slangs}}
{{> slang}}
{{/each}}
It loops through the array and inserts a slang template for each value. The slang template is shown below:
<template name="slang">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"><span class="btn">{{ slang }}</span></h3>
</div>
<div class="panel-body">
<p> {{ definition }} </p>
</div>
</div>
</template>
Data Storage
Currently, our data is stored in an array. Let's move our data to MongoDB. Meteor uses MongoDB by default. Meteor provides a way of storing and manipulating data from both the client and server side. However, there are ways to ensure that no one can inject data into the database from the browser's dev tools.
Create a new imports/api
directory. We'll put our database logic in this directory.
Go ahead and create an imports/api/slangs.js
file and add code to it like so:
import { Mongo } from 'meteor/mongo';
export const Slangs = new Mongo.Collection('slangs');
Import the module on the server to enable the creation of the collection and data-sending to the client.
server/main.js
import { Slangs } from '../imports/api/slangs.js';
import { Meteor } from 'meteor/meteor';
Meteor.startup(function () {
// code to run on server at startup
});
Update body.js
to fetch slangs from the Slangs collection rather than a static array.
imports/ui/body.js
import { Template } from 'meteor/templating';
import { Slangs } from '../api/slangs.js';
import './body.html';
Template.body.helpers({
slangs() {
return Slangs.find({}, { sort: { createdAt: -1 } });
},
});
Note: Run the app. Nothing seems to appear again. Yes, nothing shows because our database is currently empty.
Let's add data to the database. We can decide to enter data from the mongo console via the terminal or we can write a script. The former is very tedious.
Go to server/main.js and update code to be like so:
import { Slangs } from '../imports/api/slangs.js';
import { Meteor } from 'meteor/meteor';
Meteor.startup(() => {
Slangs.insert({slang: "Yoruba Demon", definition: "Nigerian guy (yoruba) who goes after a young lady's heart with no intention of loving her. They are typically met at parties, and would mostly wear white agbada."});
Slangs.insert({slang: "Bye Felicia", definition: "The perfect dismissal phrase for waving goodbye to someone or something unimportant."});
Slangs.insert({slang: "GOAT", definition: "An acronym for praising someone doing well in a certain activity. 'Greatest of all time'."});
Slangs.insert({slang: "Low key", definition: "Keeping some activity or news under wraps."});
});
When the server starts up, it automatically inserts the data defined here into the database. Now, run your app again, you should see the data. Slick!
Note: Once it populates the database once, go ahead and remove the code to avoid duplicate insertion of data.
Add New Slangs
Let's add a form to our app to enable users to add new slangs. Within the body tag, update the code to be like so:
imports/ui/body.html
...
<div class="container">
<header>
<h1 class="text-center">Slang Bucket</h1>
</header>
<div class="col-sm-12">
<form class="new-slang">
<div class="form-group">
<input type="text" name="slang" class="form-control" placeholder="Add new slangs" required="required" />
</div>
<div class="form-group">
<textarea class="form-control" name="definition" placeholder="Add new slang definitions" required></textarea>
</div>
<div class="form-group">
<input class="btn btn-small btn-info" type="submit" value="Add New Slang" />
</div>
</form>
{{#each slangs}}
{{> slang}}
{{/each}}
</div>
</div>
...
Add the JavaScript code to listen to the submit event on the form:
imports/ui/body.js
...
Template.body.events({
'submit .new-slang'(event) {
// Prevent default browser form submit
event.preventDefault();
// Get value from form element
const target = event.target;
const slang = target.slang.value;
const definition = target.definition.value;
// Insert a task into the collection
Slangs.insert({
slang,
definition,
createdAt: new Date(), // current time
});
// Clear form
target.slang.value = '';
target.definition.value = '';
},
});
In the code above, it listens to the submit event of the form, grabs the values and inserts them into the database. Run your app and try it out. Yes, it works!
Delete Slangs
Let's add functionality to delete existing slangs. We need to move the slang template to its own file. Create two files: imports/ui/slang.html
and imports/ui/task.js
.
imports/ui/slang.html
<template name="slang">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"><span class="btn">{{ slang }}</span> <button class="delete btn btn-danger pull-right">×</button></h3>
</div>
<div class="panel-body">
<p> {{ definition }} </p>
</div>
</div>
</template>
Note: Make sure you remove the slang template that was in the
body.html
file.
imports/ui/slang.js
import { Template } from 'meteor/templating';
import { Slangs } from '../api/slangs.js';
import './slang.html';
Template.slang.events({
'click .delete'() {
Slangs.remove(this._id);
},
});
In the code above, we imported the slang template, slang.html
, and we have a click event that invokes the remove
method when a user clicks on a slang's delete button.
this
refers to an individual slang object in the collection. _id
is the unique field that MongoDB assigns to a document in a collection. With this _id
, we can do almost anything: delete
, update
, and create
.
One more thing. Import slang.js
into the body.js
file:
...
import './slang.js';
...
Run your app, click the delete button on any slang and watch it disappear instantly. It removes it from the UI and deletes it from the database.
Add Authentication
Meteor ships with an authentication system. Go ahead and install the auth packages via the terminal:
meteor add accounts-ui accounts-password
Add the authentication drop-down widget to the body.html file like so:
<body>
<div class="container">
<header>
<h1 class="text-center">Slang Bucket</h1>
</header>
<div class="col-sm-12">
{{> loginButtons}}
....
Create an imports/startup/accounts-config.js
file and add the code below to it like so:
import { Accounts } from 'meteor/accounts-base';
Accounts.ui.config({
passwordSignupFields: 'USERNAME_ONLY',
});
Also import the imports/startup/accounts-config.js
file in client/main.js
:
import '../imports/startup/accounts-config.js';
import '../imports/ui/body.js';
Right now, we should be able to create an account. However, authentication is useless if we can't restrict access to functionality. Let's make sure only registered users can add new slangs. In addition, we can also reference the username of the user that added a slang.
A quick breakdown. We'll need to add new attributes to our Slang
collection.
- adderID: this will hold the
_id
of the user that added the slang. - username: this will hold the
username
of the user that added the slang.
Note: There are other efficient ways to handle the authentication schema of this app. However, for the sake of this tutorial, we'll keep things simple.
Open up imports/ui/body.js
and modify it like so:
imports/ui/body.js
import { Meteor } from 'meteor/meteor';
...
...
// Insert a task into the collection
Slangs.insert({
slang,
definition,
createdAt: new Date(), // current time
adderID: Meteor.userId(),
username: Meteor.user().username,
});
...
Open up imports/ui/body.html
and modify it like so:
...
{{> loginButtons}}
{{#if currentUser}}
<form class="new-slang">
<div class="form-group">
<input type="text" name="slang" class="form-control" placeholder="Add new slangs" required="required" />
</div>
<div class="form-group">
<textarea class="form-control" name="definition" placeholder="Add new slang definitions" required></textarea>
</div>
<div class="form-group">
<input class="btn btn-small btn-info" type="submit" value="Add New Slang" />
</div>
</form>
{{/if}}
...
In the code above, we added the {{#if currentUser}} block helper. currentUser
is a built-in helper that refers to the logged-in user. If the user is logged-in, show the add new slang form, or else hide the form.
Now, run your app.
User not logged in
No user is logged in, so no form to add new slangs. Now, create an account.
User about to log in
User is logged in
Here, the user is logged in, so he or she is able to create a new slang.
One more thing, let's display the username of the logged-in user next to the slang.
Update imports/ui/slang.html
to the code below:
<template name="slang">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"><span class="btn">{{ slang }}</span><button class="delete btn btn-danger pull-right">×</button></h3>
<span>@{{username}}</span>
</div>
<div class="panel-body">
<p> {{ definition }} </p>
</div>
</div>
</template>
Username displayed next to Slang
Eliminate Client Update
Meteor is robust. They factored in the fact that people usually create quick demos so a user can update the database directly from the client side. However, in a real-world project, you want to be sure that the server validates everything that comes into the app and allows users to complete an action only if they are authorized!
The first step is to remove the insecure
package. Meteor ships with this built-in package. This is the package that allows us to edit the database from the client.
meteor remove insecure
Next, we need to add some code to ensure validation happens right before the database methods are executed.
Open up imports/api/slangs.js
. Add code to it like so:
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const Slangs = new Mongo.Collection('slangs');
Meteor.methods({
'slangs.insert'(slang, definition) {
check(slang, String);
check(definition, String);
// Make sure the user is logged in before inserting a task
if (! Meteor.userId()) {
throw new Meteor.Error('not-authorized');
}
Slangs.insert({
slang,
definition,
createdAt: new Date(),
adderID: Meteor.userId(),
username: Meteor.user().username,
});
},
'slangs.remove'(slangId) {
check(slangId, String);
Slangs.remove(slangId);
},
});
Next, let's update the sections of the app that were executing some database operations.
imports/ui/body.js
...
...
Template.body.events({
'submit .new-slang'(event) {
// Prevent default browser form submit
event.preventDefault();
// Get value from form element
const target = event.target;
const slang = target.slang.value;
const definition = target.definition.value;
// Insert a slang into the collection
Meteor.call('slangs.insert', slang, definition);
// Clear form
target.slang.value = '';
target.definition.value = '';
},
});
We replaced the slang insert section with Meteor.call('slangs.insert', slang, definition);
.
imports/ui/slang.js
...
Template.slang.events({
'click .delete'() {
Meteor.call('slangs.remove', this._id);
},
});
We replaced the slang remove code with Meteor.call('slangs.remove', this._id);
.
Meteor.call
sends a request to the server to run the method in a secure environment via an AJAX request.
Security Concerns - Filter Data
With an emphasis on security, we need to control which data Meteor sends to the client-side database. Go ahead and remove the autopublish
package via the terminal:
meteor remove autopublish
Without the autopublish
package, no data will be sent to the client and no access will be granted to the database. Therefore, the app will not show any data on the screen. To combat this scenario, we'll have to explicitly request the data from the server to the client. Meteor uses the Meteor.publish
and Meteor.subscribe
methods to accomplish this feat.
Open imports/api/slangs.js and add this code to it:
...
...
if (Meteor.isServer) {
// This code only runs on the server
Meteor.publish('slangs', function tasksPublication() {
return Slangs.find();
});
}
...
The code above adds a publication for all slangs. Next, let's subscribe to this publication.
Open imports/ui/body.js and add this code to it:
...
Template.body.onCreated(function bodyOnCreated() {
Meteor.subscribe('slangs');
});
...
In the code above, it subscribes to the slangs publication once the body template is created. Now, our app is secure.
Run the app again. Everything should be in full working order!
Extra Functionality - Packages
There are lots of packages available for Meteor on AtmosphereJS. If there is a feature you want to implement, there is a high probability that it has been done by a developer before now and made available as a package. Explore!
Securing Meteor Applications with Auth0
Meteor is a hybrid framework that takes care of your client and server needs. In addition, it's very easy to create server-side APIs. Right now, let's go ahead and secure our Meteor API with JSON Web Tokens.
JSON Web Tokens, commonly known as JWTs, are tokens that are used to authenticate users on applications. This technology has gained popularity over the past few years because it enables backends to accept requests simply by validating the contents of these JWTs. That is, applications that use JWTs no longer have to hold cookies or other session data about their users. This characteristic facilitates scalability while keeping applications secure.
"Applications that use JWTs no longer have to hold cookies or other session data about their users."
Tweet This
Whenever the user wants to access a protected route or resource (an endpoint), the user agent must send the JWT, usually in the Authorization header using the Bearer schema, along with the request.
When the API receives a request with a JWT, the first thing it does is to validate the token. This consists of a series of steps, and if any of these fail, the request must be rejected. The following list shows the validation steps needed:
- Check that the JWT is well-formed
- Check the signature
- Validate the standard claims
- Check the Client permissions (scopes)
We will make use of Auth0 to issue our JSON Web Tokens. With Auth0, we have to write just a few lines of code to get a solid identity management solution, including single sign-on, user management, support for social identity providers (like Facebook, GitHub, Twitter, etc.), enterprise (Active Directory, LDAP, SAML, etc.), and your own database of users.
For starters, if you haven't done so yet, this is a good time to sign up for a free Auth0 account. Having an Auth0 account, the first thing that we must do is to create a new API on the dashboard. An API is an entity that represents an external resource, capable of accepting and responding to protected resource requests made by clients. And we are dealing with an API here, SWAPI (Star Wars API).
Auth0 offers a generous free tier to get started with modern authentication.
Login to your Auth0 management dashboard and create a new API client.
Click on the APIs menu item and then the Create API button. You will need to give your API a name and an identifier. The name can be anything you choose, so make it as descriptive as you want.
The identifier will be used to identify your API, and this field cannot be changed once set. For our example, I'll name the API, Slang API, and for the identifier, I'll set it as https://slangsapi.com. We'll leave the signing algorithm as RS256 and click on the Create API button.
Create a New API
Creating the Slangs API
Head over to your terminal and install the following node modules:
meteor npm install express express-jwt jwks-rsa --save
Open your server/main.js
file. Add this code at the top:
import express from 'express';
import jwt from 'express-jwt';
import { expressJwtSecret } from 'jwks-rsa';
const app = express();
WebApp.connectHandlers.use(app);
In the code above, we imported express
, express-jwt
, and jwks-rsa
.
- The
express-jwt
module is an express middleware that validates a JSON Web Token and sets thereq.user
with the attributes. - The
jwks-rsa
module is a library that helps retrieve RSA public keys from a JSON Web Key Set endpoint.
Then the code just below the imports
statements starts up express server and hooks it into the port Meteor uses:
...
const app = express();
WebApp.connectHandlers.use(app);
Next, go ahead and add the following code:
server/main.js
...
const authCheck = jwt({
secret: expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
// YOUR-AUTH0-DOMAIN name e.g https://prosper.auth0.com
jwksUri: "{YOUR-AUTH0-DOMAIN}/.well-known/jwks.json"
}),
// This is the identifier we set when we created the API
audience: '{YOUR-API-AUDIENCE-ATTRIBUTE}',
issuer: '{YOUR-AUTH0-DOMAIN}',
algorithms: ['RS256']
});
app.get('/api/slangs', (req, res) => {
const slangs = Slangs.find().fetch();
res.status(200).json({ message: slangs });
});
Note: Replace the YOUR-API-AUDIENCE-ATTRIBUTE
and YOUR-AUTH0-DOMAIN
placeholders with the API audience and Auth0 domain values from your Auth0 dashboard.
Run your app by going to the /api/slangs
route. You should see the set of slangs displayed.
Now, go ahead and modify the route code by adding the authCheck
variable as a middleware.
server/main.js
...
app.get('/api/slangs', authCheck, Meteor.bindEnvironment(function(req, res) {
const slangs = Slangs.find().fetch();
res.status(200).json({ message: slangs });
});
The authCheck
variable does the check to validate the access tokens that are sent as Authorization headers. It validates the audience
, issuer
and algorithm
used to sign the token.
Now, run your app with Postman again.
Accessing the endpoint without an access token
Now, let's test it with a valid access token. Head over to the test
tab of your newly created API on your Auth0 dashboard.
Grab the Access token from the Test tab
Grab the Access Token
Now use this access token
in Postman by sending it as an Authorization header to make a GET request to api/slangs
endpoint.
It validates the access token and successfully makes the request.
Conclusion
Well done! You have learned how to build a real-time web app with Meteor, and authenticate it using JWTs. It's a platform that enables you to cut down on development time. Meteor makes it incredibly easy to also flesh out an API like you can do with KeystoneJS and Loopback.
"Meteor is a platform that enables you to cut down on development time."
Tweet This
In addition, Auth0 can help secure your API easily. Auth0 provides more than just username-password authentication. It provides features like multifactor auth, breached password detection, anomaly detection, enterprise federation, single sign on (SSO), and more. Sign up today so you can focus on building features unique to your app.
Please, let me know if you have any questions or observations in the comment section. 😊