Contents
- Introduction
- Looking back to the future
- Deep dive into the Sales Portal code
- Improving Sales Portal access security
- Application deployment in the cloud
- Foto Share: A mobile photo-sharing service
- Meteor Remote Methods: Custom RPCs made simple
- A platform for building modern web applications
- Conclusion
- Downloadable resources
- Related topics
- Comments
Instant web applications with Meteor
Build responsive web applications, from concept to scaled deployment, in record time
Meteor, a platform for web application development, is gaining significant international adoption. More than a JavaScript coding framework, Meteor provides an innovative way to construct scalable, rich, interactive web applications. Meteor promises to turbocharge the development cycle by simplifying the coding model and reducing the amount of code that developers must write. Using Meteor, experienced web application architects and developers can go from concept to full deployment in weeks, or even days.
For a step-by-step guide on installing and starting to develop with the Meteor platform, follow the Quick Start docs at the Meteor website. In this tutorial, I take you deeper into Meteor development with two detailed application examples and give you an overview of Meteor's approach to web app development. With this knowledge, you'll be able to decide for yourself if rapid web application creation with Meteor suits your needs.
Looking back to the future
The approach that Meteor uses is revolutionary in a sense, yet it has some evolutionary aspects too. It continues along the same IT path as one of the great successes in computing history — spreadsheet software. Figure 1 shows a typical spreadsheet example: a Sales by Region spreadsheet with a pie chart.
Figure 1. The Sales by Regions spreadsheet

If you modify any of the regional sales figures in the Sales by Region spreadsheet, the pie chart instantaneously redraws to reflect the new relative proportions of the slices.
This spreadsheet capability is no longer novel, but imagine its impact back in 1983 when Lotus 1-2-3 unveiled the feature. Never before could early PC users do so much work without doing any programming. Spreadsheet software is still one of the killer apps driving PC sales worldwide.
Fast-forward three decades
This tutorial's first web application example — the Sales Portal app shown in Figure 2 — demonstrates how the spreadsheet metaphor has evolved with Meteor, circa 2015.
Figure 2. Sales Portal web application

Sales Portal shows up-to-the-minute regional sales figures and a corresponding pie chart. The global sales director can use the app to monitor sales, and each regional sales team can update its sales figure whenever it makes a sale.
You can download the Sales Portal application and
go hands-on with it if you have Meteor installed. Change to the download's
sales_nologin subdirectory and type meteor
. Point a browser
instance at http://localhost:3000/ to display the regional sales figures
and pie chart. (If you're unable to install Meteor, you can run the IBM Bluemix hosted
version.) Click any sales figure to change it. The pie chart
updates immediately after you confirm your change. If you point multiple
browser instances at the Sales Portal, all of them update to show the
latest sales figures, and you can modify the figures from any one of the
browser instances.
Figure 3 shows the US Central team selecting and updating its sales figure.
Figure 3. Updating US Central sales

Figure 4 shows the updated US Central sales figure and the up-to-date pie chart. Any user concurrently accessing the Sales Portal at the time of the update sees the change immediately.
Figure 4. Pie-chart proportions updated to reflect new US Central sales figure

Instead of manual updates, you can also imagine a back end where the sales figures consist of subaggregates that are gathered and consolidated autonomously before updating. The visual presentation of the Sales Portal application is identical to that of the venerable spreadsheet, but Sales Portal now also has:
- Global Internet access via the ubiquitous browser
- Simultaneous access by multiple users
- Optional automated back-end data aggregation and consolidation
Significant effort would be required to design, code, and deploy such a system using standard enterprise technology (say, a Java™-based tool chain). Meteor minimizes this effort dramatically, as you'll see in the code walkthrough that follows.
Reactive thinking
“This approach greatly reduces the amount of infrastructure code that you need to write, debug, and test. ”
A key feature of the spreadsheet is its reactive nature. In the Sales by Region example, when a regional sales figure is updated, all other values that depend on that figure are recalculated on the fly. If the dependent component renders graphical output, such as the pie chart, the chart is immediately redrawn with updated slice sizes. You needn't write the code that manages the dependencies (which can be complex) or the code to update the components such as the pie chart. All you do is declare the reactive elements (the sales figures) and their dependencies (the pie chart in this case). The spreadsheet takes care of it all.
Now imagine doing the same today with a web application, and you've got a good picture of how Meteor simplifies web-based system creation.
When you design a Meteor application, you decide on the reactive elements — say, the regional sales data collection. Then, you lay out your presentation layer using standard HTML, CSS, client-side JavaScript libraries and components (such as JQuery, JQuery UI, or JQuery Mobile), and templating technology such as Spacebars (similar in concept to JavaServer Pages but typically runs on the client side). Meteor tracks all dependencies of the reactive elements and then rerenders visual elements and recomputes dependencies to reflect the latest updated values.
This approach greatly reduces the amount of infrastructure code that you need to write, debug, and test. You don't need to write the custom back-end web services to sync the update request, or the code to update the database or data store, or the code to push change notifications out to other connected clients, or the code to fetch updated values from the back end upon notification.
Deep dive into the Sales Portal code
Listing 1 shows the sales.js file, which contains all the server and client-side logic behind the Sales Portal application. This is the only JavaScript code that I needed to write for this application. (You can find sales.js in the sales_nologin directory of the code download.)
Listing 1. Client- and server-side logic for Sales Portal: sales.js
SalesData = new Meteor.Collection("regional_sales"); if (Meteor.isClient) { Template.salesdata.helpers({ dataset : function () { return SalesData.find({}, {sort: {row : 1}}); } }); Template.datapoint.helpers({ selected : function () { return Session.equals("selected_datapoint", this._id) ? "selected" : ''; } }); Template.title.helpers({ thisyear: new Date().getFullYear() }); Template.datapoint.events({ 'click': function (event, template) { Session.set("selected_datapoint", this._id); } }); Template.piechart.helpers({ 'plotchart' : function() { plotit(this, SalesData.find({})); } }); function plotit(inst,cur) { if (cur.count() === 0) // do not render pie if no data return; var data = []; cur.forEach( function(sale) { data.push( [sale.region, sale.total]); }); window.$.jqplot('chart', [data], { seriesDefaults: { // Make this a pie chart. renderer: $.jqplot.PieRenderer, rendererOptions: { // Put data labels on the pie slices. // By default, labels show the percentage of the slice. showDataLabels: true } }, legend: { show:true, location: 'e' } } ); } Template.datapoint.onRendered(function() { this.$('.editable').editable( { autotext: 'never', type: 'text', width: 10, mode: 'inline', success: if (newvalue != "") { var testint = Number(newvalue); if (Math.floor(testint) == newvalue) { SalesData.update(Session.get("selected_datapoint"), {$set: {total: parseInt(newvalue)}}); } else { return ({newValue: $(this).text()}); } } else { return ({newValue: $(this).text()}); }, display: function() { // dummy display function so 'text' is not set, Blaze will set it } }); }); } // if isClient if (Meteor.isServer) { Meteor.startup(function () { if ((process.env['CLEARDATA']) || (SalesData.find().count() == 0)) { var roworder = 0; SalesData.remove({}); SalesData.insert({region:"US East", total: 2032333, row: roworder++}); SalesData.insert({region:"US Central", total: 150332, row: roworder++}); SalesData.insert({region:"US West", total: 1202412, row: roworder++}); SalesData.insert({region:"Asia Pacific", total: 701223, row: roworder}); } }); }
The conditionals around the Meteor.isClient
and
Meteor.isServer
variables are runtime context indicators
provided by the Meteor core and usable anywhere in your code. In this
case, they allow the combination of client and server-side code within the
same sales.js file. Any code outside of the conditionals runs on both
client and server.
Alternatively, you can separate the client and server source code by placing code served to a client in a subdirectory named client and server-side code in a subdirectory named server. In that scenario you put any assets (such as images) that are served to the client in a subdirectory named public. If you want to restrict certain resources to server access only (not serve them to the client), put them in a subdirectory named private. (The photo-sharing app that you'll work with later in this tutorial uses that directory structure.)
Identifying the reactive data
The selected_datapoint
session variable in Listing 1 is reactive. (See the Meteor reactive defaults sidebar for more information on elements that are
reactive by default.) The variable is used in this case to change the
sales figures' row highlighting. Row highlighting is performed via changes
in dynamic CSS styles. The selected_datapoint
session
variable is updated when a user clicks a row. Because Meteor rerenders
dependencies every time this variable changes, the highlight is updated
accordingly. Note that selected_datapoint
exists only on the
client side.
Another reactive source of data for the Sales Portal application is a query
on the SalesData
Meteor collection. Unlike
selected_datapoint
, SalesData
exists on both
client and server; on the server, it's a MongoDB collection. You can see
the use of SalesData
in Listing
1.
Because the query is reactive, all of its dependencies are recomputed or rerendered when the query result set changes. This is how the sales figures and the pie chart, across all browser instances, get updated. Listing 2 shows the associated HTML template code, found in the sales.html file in the sales_nologin directory of the code download.
Listing 2. Client side HTML and templates: sales.html
<template name="salesdata"> <div class="salesdata"> {{#if Template.subscriptionsReady}} {{#each dataset}} {{> datapoint}} {{/each}} {{/if}} </div> </template> <template name="datapoint"> <div class="datapoint {{selected}}"> <div id="{{_id}}_region" class="region">{{region}}</div> <div id="{{_id}}_total" data-inputclass="inputclass" class="sales editable">{{total}}</div> </div> </template>
The HTML file in Listing 2 is a Spacebars template. Spacebars is Meteor's
own templating engine. You can see the Spacebars expressions in Listing 2
inside double curly braces {{ }}
. (Through its Blaze
rendering system, Meteor can also potentially work with other JavaScript
templating engines.)
The rows of sales figures are rendered via the salesdata
template code shown in Listing 2. Because this
template depends on the dataset
helper function, shown in
Listing 1, it's rerendered every time the
contained reactive query changes.
Seeding sample data on the server
The initial regional sales data for Sales Portal is seeded by the server-side code (from Listing 1) that's shown in Listing 3.
Listing 3. Server-side code to seed data in MongoDB
SalesData.remove({}); SalesData.insert({region:"US East", total: 2032333, row: roworder++}); SalesData.insert({region:"US Central", total: 150332, row: roworder++}); SalesData.insert({region:"US West", total: 1202412, row: roworder++}); SalesData.insert({region:"Asia Pacific", total: 701223, row: roworder});
On the Meteor server, a full MongoDB instance is in operation. (This full instance can accept queries and updates from clients other than Meteor.) The same JavaScript MongoDB API is also available on the client side, unifying the client and server coding and enabling code reuse on both client and server. The client-side API is provided by a Mongo emulator called Minimongo. Minimongo uses latency compensation to reflect database changes. Because Minimongo typically deals with small client-side data sets, it doesn't support indexing.
An asynchronous publish/subscribe model is used to control the data that's synchronized between the MongoDB server and the Minimongo client. By default, all server-side Meteor collections are published. Meteor uses Distributed Data Protocol (DDP) to move data between client and server. (DDP drivers in the form of client-side emulator and server-side provider can be created for other databases; ongoing efforts in the Meteor community include a PostgreSQL driver and a RethinkDB driver).
Integrating jQuery plugins
Sales Portal uses the jqPlot jQuery
plugin to render the pie chart. The rendering and rerendering of the pie
chart is driven reactively by data changes in the SalesData
collection. You saw earlier that the salesdata
template is
rerendered every time the SalesData
collection changes.
Listing 4 shows the client-side function (from Listing 1) that redraws the pie chart when triggered by the
salesdata
template's rendered
event.
Listing 4. jQuery code to render the pie chart, using the jqPlot plugin
window.$.jqplot('chart', [data], { seriesDefaults: { // Make this a pie chart. renderer: $.jqplot.PieRenderer, rendererOptions: { // Put data labels on the pie slices. // By default, labels show the percentage of the slice. showDataLabels: true } }, legend: { show:true, location: 'e' } } );
Sales Portal uses the x-editable jQuery plugin to enable in-place editing of the sales
figures. The code that handles editing appears inside the
Template.datapoint.onRendered()handler
in Listing 1.
Improving Sales Portal access security
The default prototype mode that Meteor supports is ideal for initial rapid evolution of your applications. During that phase, you are likely modifying the interactions, the UI, and even application logic in rapid iterations — typically without involving any sensitive data. You can share the URL with collaborating developers and reviewing users to gather feedback. But anyone who has the Sales Portal URL can see and even change the sales data. This open-access model is too permissive for production use cases.
The natural next step is to lock the application down by turning on Meteor's security features. The code for a more secure version of the Sales Portal is in the download's sales subdirectory. The added security features are:
- A user-authentication system to allow only authorized user access to the portal
- Code that ensures that only an official owner of a region's sales data can modify that data
- Better source code organization to ensure that potentially secret server-side code is never served to deployed clients
From this point on, all my references to the Sales Portal application are to the secure version that's in the sales directory.
Removing the ability for clients to modify server data
A good start for locking the application down is to prevent anyone from
modifying the data. All you need to do in this case is to remove the
insecure
package with this command:
meteor remove insecure
The insecure
package tells the server not to check access
rules before reading or changing data. This package is installed by
default, allowing all access. After you remove it, no client can modify
any server data. If you go back to one of the browser instances and try to
modify any of the sales data, you'll notice that the application tries to
change the data but quickly reverts, reflecting the denial of access from
the server. (This is an example of latency
compensation in action. The data is actually updated for a moment
in the client, but when the authoritative server copy arrives, it
overwrites the client's data.)
After you remove the insecure
package, you must add access
rules to explicitly allow access to specific pieces of data (by specific
users). But you have no users yet. A user database and login-authorization
system must be added next.
Ensuring that only authorized users can view the sales data
Before you add the user-authorization system, make sure that nobody can see the sales data. (Authorized users will be allowed to see it later.) Right now, even though they can't modify the data, anyone can view it by going to the Sales Portal URL.
Remove the default autopublish
package to prevent any Meteor
collection data from being pushed from the server to the client —
except data that is explicitly published by the server and subscribed to
by the client:
meteor remove autopublish
If you visit the Sales Portal URL now, the regional sales data and pie chart aren't visible.
Adding user login via the accounts
package
Meteor has available packages that make adding a user login and
authorization system straightforward. The accounts-ui
and
accounts-password
packages cover end-to-end workflow; they
include the front-end UI, back-end database, and client-to-server APIs
that are required. You can add all of these features to Sales Portal with
one command:
meteor add accounts-password accounts-ui
The account-password
package supports user creation and login
via email-address-plus-password. The implementation uses the Secure Remote Password protocol,
and plain-text passwords are never sent between the client and server.
In addition to password-based login, you can also let your users sign in via Facebook, Twitter, Weibo, GitHub, and Google — simply by adding one or more packages to your applications. (Social network OAuth login support is valuable for consumer-facing applications but likely has limited applicability in enterprise intranet environments.)
Drop-in UI for login
The accounts-ui
package provides a prefabricated set of
CSS-styled UI widgets (and supporting JavaScript code) to handle user
login, new-user creation, and lost-password recovery. To add these
widgets, include the {{>loginButtons}}
Spacebars template.
Listing 5 shows login added to the Sales Portal application in part of the
sales/sales.html file.
Listing 5. Adding a user login and authorization system
<body> <div id="title"> <div> {{> title }} </div> <div> <div style="float: right"> {{> loginButtons align="right"}} </div> </div> </div> </div>
The sales directory in the sample-code download contains the completed Sales Portal application with user access control. Run this version (or the Bluemix-hosted version) to try logging in. Start a browser instance, and notice the Sign in link that's now in the top right-hand corner. Click it, and you should see the dialog box shown in Figure 5.
Figure 5. Sign-in dialog box from the
accounts-ui
package (shown in Firefox)

The accounts-ui
package uses Meteor collections and
publish/subscribe — the same facilities that are also available for
you to use manually in your own code — to implement the user
database. In the current version of Sales Portal, I've created two sets of
user credentials (email address / password) in the database:
joe@dwtestonly.com / abc123 and sing@dwtestonly.com / abc123. Open two
browser instances and log in with one credential each.
You can create more users by clicking the Create account
link in the Sign in dialog box. Figure 6 shows the
create-new-user dialog box, which is a part of the
accounts-ui
package.
Figure 6. Dialog box for creating new user
accounts from the accounts-ui
package (shown in
Chrome)

Adding owner
field to regional
sales data
In the current version of Sales Portal, the initial database content has been modified. I used the server-side code in Listing 6 to seed the data.
Listing 6. Server-side data-seeding code
SalesData.remove({}); SalesData.insert({region:"US East", total: 2032333, row: roworder++}); SalesData.insert({region:"US Central", total: 150332, row: roworder++, owner: joeid}); SalesData.insert({region:"US West", total: 1202412, row: roworder++}); SalesData.insert({region:"Asia Pacific", total: 701223, row: roworder});
A new owner
field was added in the Listing 6 code. In this
case, the owner
field contains the user ID of the user
(joe@dwtestonly.com) who owns the US Central region data. This field is
used to restrict regional sales data updates only to joe@dwtestonly.com.
You can obtain user ID values by querying the Meteor.users
collection.
Fine-grained selective server-data publishing
With the autopublish
package removed, it's necessary to
publish data from the server explicitly, and explicitly subscribe to it
from the client.
For Sales Portal, the server publishes the regional_sales
collection using the code in Listing 7, which is part of the
sales/server/sales.js file.
Listing 7. Selectively publishing data from the server
Meteor.publish("regional_sales", function () { if (this.userId) { // only visible to logged in users // do not include the owner field for client access return SalesData.find({}, {fields: {"region": 1, "total":1 }}); } });
In Listing 7, note the use of this.userId
to ensure that a
valid user is logged in to the client-side session. When Meteor runs
server code on behalf of a user, this.userId
always contains
the unique ID of the current logged-in user. If the current browser
instance has no logged-in user, this.userId
is null, and no
data is published. Furthermore, in Listing 7 not all the fields of a
regional sales data document (a document being essentially a
record with a variable number of fields in a MongoDB instance) are
returned in the collection sent to the client. Specifically, the
document's owner
field is hidden from the client. This is
how, using a query, you can publish only a subset of the collection
containing a subset of the fields, only to a client with authorized
logged-in users. This technique is useful for ensuring that sensitive data
fields in certain documents are inaccessible to client browsers.
Client-side data subscription
The Sales Portal client code explicitly subscribes to the server-published
regional_sales
collection, as shown in Listing 8. Meteor
supports subscription on a per-template-instance basis. Using per-template
subscription ensures that only data rendered by a template instance is
pushed, and that subscriptions are removed correctly when a template
instance is destroyed.
Listing 8. Per-template client subscriptions to a collection from server
Template.piechart.onCreated(function() { this.subscribe("regional_sales"); }); Template.salesdata.onCreated(function() { this.subscribe("regional_sales"); });
Adding access rules to allow regional sales updates
With the insecure
package removed, all users are effectively
denied from updating the sales data. Assuming the different regional sales
figures are owned by different users, an access rule can be added to allow
an update of the US Central data by joe@dwtestonly.com. Listing 9 shows this
access rule in the server-side source file named model.js.
Listing 9. Server-side access rule allowing owner to update sales figure
SalesData.allow({ update: function (userId, sales, fields, modifier) { if (userId !== sales.owner) return false; // not the owner var allowed = ["total"]; if (_.difference(fields, allowed).length) return false; // tried to write to forbidden field return true; }, });
The access-rule function for the update
operation returns
true
if an update is allowed and false
if it isn't
allowed. The code in Listing 9 checks to make sure that the user is the
owner, and that only the total
field is being modified.
Open Sales Portal and log in as joe@dwtestonly.com. Try to modify the US West data, and notice that you can't. Then try to modify the US Central data. Because joe@dwtestonly.com is the owner of this data, you can update it.
Start another browser instance and log in as sing@dwtestonly.com. Try modifying any of the sales data and notice that you can't. Because sing@dwtestonly.com isn't an owner of any of the sales data, the server denies all modification requests from that user.
You can use the client-side currentUser
helper to avoid
rendering the templates if the user isn't logged in. Add the code in
Listing 10 to the HTML file.
Listing 10. Eliminating attempts to render an empty template
<div id="container"> <div id="salestable"> {{#if currentUser}} {{> salesdata}} {{/if}} </div> <div> {{#if currentUser}} {{> piechart}} {{/if}} </div> </div>
Now, start a new Sales Portal browser instance. Note that no data is visible. Log in as sing@dwtestonly.com, and notice that now you can see the data and pie chart. Now try to modify a field; again, you can't modify it because you are not the owner.
Start another browser instance, log in as joe@dwtestonly.com, and note that the data is visible. Modify the US Central figure, and notice that the pie chart updates. Confirm that the pie chart in the sing@dwtestonly.com session has also changed. Sign out of the two sessions and notice that the data now disappears.
Application deployment in the cloud
Deploying Meteor applications to Bluemix is straightforward. Follow the instructions in my screencast:
You can also use a built-in Meteor command to deploy your application onto Meteor's cloud-hosted server. As of this writing, this service is free of charge. You issue this command from your application directory:
meteor deploy applicationname.meteor.com
The application name must be unique, because it will become available (globally over the Internet) as http://applicationname.meteor.com. You will be prompted to create an account on meteor.com the first time you deploy an app.
At the time of this writing, the Meteor team is working on a new product named Galaxy: a scalable deployment platform based on the Kubernetes orchestration of Docker containers.
To host the application on your own server infrastructure, you need a server with Node.js and MongoDB facilities preinstalled. You can create a deployable bundle of your application using the command:
meteor build directory to place deployable bundle
Because the server-side deployable is a Node.js application, you can customize the deployment topology to your own interoperation or scaling requirements.
The Meteor server-side code runs on Node.js fibers (see the Fibers on Node.js sidebar), providing a virtual environment where you can code as if one thread were handling each incoming request (with no sharable state). This approach can simplify the coding of server-side JavaScript logic.
Foto Share: A mobile photo-sharing service
Now that you've seen where a little design and planning with a bit of Meteor code can take you, you might be thinking up a startup project or two already. The next example will give you more ideas to apply while creating Meteor applications for mobile devices.
Foto Share is a proof-of-concept web-based photograph-sharing service for mobile phone users. On their phones, users can browse through a collection of their own photos and share photos with their friends via one tap of the Share button. Figure 7 shows Foto Share running on an Apple iPhone.
Figure 7. Foto Share on Apple iPhone

Just as in the Sales Portal project, and for the same security reasons, the
autopublish
and insecure
packages have been
removed from Foto Share. And to implement password-based login, Foto Share
has added the accounts-ui
and accounts-password
packages. The Foto Share application in the code download has the same two
users as the Sales Portal.
To try out Foto Share, first run the application from the fotoshare directory in the code download. (If you don't have Meteor installed, you can run the Bluemix-hosted version.) If you have two phones, point each one's browser to Foto Share. Otherwise, continue to use PC browsers. Sign in as sing@dwtestonly.com on one and joe@dwtestonly.com on the other (using the same password you used for Sales Portal). You can go through Sing's photos by swiping through them, or touch the overlay on either side. Each new photo slides into view. You will discover that all of Sing's pictures are of scenes from Hawaii, and that Joe's are of Aztec and Mayan artifacts.
When you're ready to test the sharing feature, choose a picture in Joe's collection and touch the Share button on the phone. On Sing's phone, Meteor reactively updates the subscribed collection, and Joe's shared picture is now viewable on Sing's phone.
Foto Share user login UI
It would be great to be able to drop in a customizable mobile login UI, as
you can for Meteor web applications, but Meteor doesn't (yet) have a
mobile friendly accounts-ui
package. For Foto Share, I used
the accounts-ui
web UI. Figure 8 shows the login dialog box as
presented on a mobile phone.
Figure 8. Foto Share login screen

Identifying reactive data in Foto Share
Conceptually, the most natural data collection to make reactive is the set of user's photos. Doing so would enable Meteor to update and rerender the list of photos whenever a user shares his or her photos. This is the approach taken in this proof of concept. For larger systems, depending on the back-end storage architecture for the images, you might want to make only the photo's metadata reactive, instead of the images themselves.
Take a look at the server-side data-seeding code, shown in Listing 11, to get an idea of how the photos are stored.
Listing 11. Foto Share server-side data-seeding and collection-publishing code
Meteor.startup(function () { ... Fotos.remove({}); Fotos.insert({name:"pic1", img: readPic('pic1.jpg'), owner: sing._id, shared:false}); Fotos.insert({name:"pic2", img: readPic('pic2.jpg'), owner: sing._id, shared:false}); Fotos.insert({name:"pic3", img: readPic('pic3.jpg'), owner: sing._id, shared:false}); Fotos.insert({name:"pic4", img: readPic('pic4.jpg'), owner: joe._id, shared:false}); Fotos.insert({name:"pic5", img: readPic('pic5.jpg'), owner: joe._id, shared:false}); Fotos.insert({name:"pic6", img: readPic('pic6.jpg'), owner: joe._id, shared:false}); Meteor.publish("photos", function () { if (this.userId) { // only visible to logged in users return Fotos.find( {$or : [{owner: this.userId}, {shared: true}]}, {fields: {"name": 1, "img":1 , "owner": 1}}); } }); });
The server-published collection is Fotos
. Each document in
Fotos
representing a photo has name
,
img
, and owner
fields. The img
field is read in from a corresponding locally stored JPG file. Here again,
notice that the data that a subscribing client receives contains only that
user's own photos, plus any other photos that are shared by their owner.
Here too, selective field filtering is used to remove the
shared
field from the Foto collections that clients receive.
The owner
field has not been filtered out, because a client
might want to show the name of the owner for a shared picture. Listing 12
shows the readPic()
helper function. The function uses
Meteor's Assets.getBinary()
helper to read the image into
memory, and then encode the binary stream into base64 for storage into the
img
field. This format is convenient for displaying the
photograph when it's retrieved on the client side. The Assets
package can
be used to access resources, available only to the server, under the
private subdirectory.
Listing 12. Helper function to read JPG images for MongoDB storage
function readPic(infile) { var data = Assets.getBinary('images/' + infile); var buf = new Buffer(data); var tp = buf.toString('base64'); return 'data:image/jpeg;base64,' + tp; }
When the images are reconstituted from the database, the template code
takes advantage of data
URL support in the <IMG>
tag on most modern
browsers. Data URL support on the SRC
attribute of an
<IMG>
tag enables the dynamic setting of the binary
bits of an image through a base64-encoded string. Listing 13 shows the
photoitem
template. In this template the base64-encoded
img
field from a photo is used to render the image.
Listing 13. Setting an IMG
tag's
SRC
attribute using data URL
<template name="photoitem"> <div class="m-item"> <img id="{{_id}}" src="{{img}}" /> </div> </template>
Meteor Remote Methods: Custom RPCs made simple
Even though the insecure
package was removed, no access rule
has been created in Foto Share. Without access rules, no client can update
data via Minimongo. But when the Share button is tapped,
the share
field of the photo must have been updated somehow.
How? The answer is Meteor Remote Methods.
Meteor Remote Methods is a Remote Procedure Call (RPC) mechanism. You can create RPC calls from the client to the server in two steps:
- Define a JavaScript function on the server side.
- Use
Meteor.call()
to invoke the server function remotely, optionally passing arguments and callback.
Meteor takes cares of all the endpoint setup, takedown, and data-marshalling chores in between.
When you tap the Share button in Foto Share, the client
calls a Meteor Remote Method on the server named
shareThisPhoto
and passes the photo's ID as an argument. On
the server side, the code first checks to see if the caller is the owner
of the photo, and it updates the photo's shared
field if and
only if the owner called the method. Listing 14 shows the server-side
shareThisPhoto
code.
Listing 14. Meteor Remote Method on the server side to
update a photo's shared
field
Meteor.methods({ shareThisPhoto: function (photoId) { var curPhoto = Fotos.findOne({_id: photoId}); if (this.userId !== curPhoto.owner) { return "Cannot share this photo."; } else { Fotos.update({_id: photoId}, {$set :{shared: true}}); return "Photo shared!"; } }, });
Listing 15 shows the client-side code that invokes the remote method when you tap the Share button.
Listing 15. Client-side code to invoke the remote method when the Share button is tapped
Template.photopage.events({ 'click .fs-logoff': function () { Meteor.logout(function() { location.reload(); }); }, 'click .fs-share': function() { var curselected = $('.m-active > img').attr('id'); Meteor.call('shareThisPhoto', curselected, function (error, retval) { console.log(retval); }); } });
I've chosen the RPC approach to demonstrate Meteor Remote Methods. You
could instead define an access rule to allow an owner to update the
shared
field. In that case, you then must locally update the
shared
field of the photo when the user taps the Share
button. Meteor's Minimongo pushes the update to the server, then to all
other subscribed clients.
A platform for building modern web applications
Now that you've worked through this article's sample applications, it should make sense that Meteor is designed for realtime asynchronous streaming-update web applications.
These types of applications typically consist of a highly interactive single-page UI. Users typically don't experience new page loads; instead, a portion of the displayed page updates instantaneously in response to user interaction (or data changes), with few or no network round-trip delays. The single-page interface in no way restricts the application, because portions of the page can be updated in an infinite variety of ways. This design is reminiscent of a stand-alone desktop application such as a word processor or spreadsheet.
These types of web applications typically load JavaScript application code on the client browser. This code manages the interaction with the user by dynamically manipulating the browser's DOM, modifying CSS styling, generating new HTML elements/code/styling, and using other browser-provided APIs. All interfacing with the user is controlled by the client-side code; no additional HTML or styling is loaded via the network except for the initial application load. The same code also shuttles data back and forth between the client and the server(s) to implement application features. In effect, the browser loads and runs a rich-client (sometimes called fat-client) application written in JavaScript.
On the server side, endpoints are set up to source and sync data from the client securely. Legacy back ends can have RPCs, XML-based web services, RESTful services, or other JSON-style RPC calls. Modern back ends are likely to be streaming data asynchronously from servers via proprietary protocols that are designed to be efficient for the data on the wire, resilient to occasional disconnection, supportive of a variety of current transports, and topologically scalable.
Conclusion
The Meteor team is working to level the playing field for everyone who wants to create single-page, highly interactive, real-time web applications. Most users prefer such applications over the legacy page-at-a-time style. Many of the major web-based mail and office services — including Facebook's Instagram, Google Gmail, Microsoft Hotmail, and Yahoo Mail — use this application architecture. We don't see many instances of these kinds of web applications other than ones developed by the biggest tech companies or most-well-funded startups because they are difficult to build, requiring significant effort and resources. Meteor's stated core mission is to provide a base on which this class of application can be built easily.
Meteor's entrance on the scene makes this a great time to revisit all those long-forgotten application ideas you have stashed away, and see if converting them into a public-facing service might be an appealing venture. If you are already creating or maintaining web applications, Meteor might prove to be the most potent tool in your ever-expanding array of options.
Downloadable resources
- PDF of this content
- Sales Portal and Foto Share code (wa-meteor-webapps.zip | 548KB)
Related topics
- Deploy Meteor apps on Bluemix
- Meteor: Explore the Meteor website.
- Meteor documentation: Check out the official Meteor documentation for the latest features and updates. The Quick Start guide can get you up and running with Meteor in no time.
- Atmosphere repository: Find thousands of community-contributed, ready-to-integrate packages that you can add to your Meteor apps.
- Deploy Meteor apps on Bluemix: Deploying meteor apps to Bluemix is straightforward. Follow the directions in this short screencast.
- MongoDB: The MongoDB NoSQL database stores JSON-style documents and has a succinct query syntax that is used extensively on both client and server Meteor application code.
- jQuery: Discover everything related to the jQuery framework, including a large list of plugins for different jQuery versions.
- Meteor: Meteor is available for download on GitHub.