Deploying Ember CLI Apps to Parse Cloud Code

1 September 2015

I’m a big fan of Parse: their backend-as-a-service works great for the type of apps I build, which generally have little to no “server” and communicate primarily through REST endpoints.

Just the other day, I discovered that their back-end can host static files, meaning I could move my deployed Ember apps away from services like S3 or Heroku, thus simplifying (and cheapening) my deployments. Getting an Ember CLI app to run on Parse was a little less than frictionless, though, and, in this post, we’ll look at how to manage it.

To get started, you’ll need:

But First, a Caveat

To be fair, deploying static files to Parse (such as those generated by Ember CLI) is quite simple. The Parse team provides a command line tool to deploy your code and assets, and it works well. What makes deploying Ember apps tricky has to do with Ember’s URLs.

By default, Ember CLI apps use a browser’s history API, which gives you URLs like your-app.com/photos/1. Here’s the issue: if a user navigates directly to that URL, most web servers will look for either static files or a defined route to serve, neither of which we’ll have after deploying this app.

$ curl -s -w "%{http_code}" http://your-app.com/photos/1 -o /dev/null
> 404 # Ruh roh.

To fix this, we can tell Ember CLI to use “hash”-based URLs instead (like your-app.com/#/photos/1), which we’ll see how to do momentarily. This tells the server to load the root document, and Ember can then step in to figure out what route you’re looking at based on what’s past the “hash” symbol.

Now that’s out of the way, let’s get started deploying our Ember CLI app to Parse.

The Easy Way

Update Ember CLI’s location type. We now know that history-based URLs will break when Parse serves the app from static files, so let’s change our app to use hashes instead: in config/environment.js, simply change the locationType property from 'auto' to 'hash'.

Get your project Parse-ready. The fastest way to do this is to create a new Parse app using their command line tool. From there, you can either copy the files you need into an existing project, or create a new Ember CLI project inline. Using the Parse CLI, run parse generate and answer its questions. You’ll see a few files in there that you’ll need in your Ember app:

For more details, check out the Parse documentation on the subject.

Deploy! This step requires a bit more prep. What we need is to compile the Ember app into a public directory, then copy the Parse files from the previous step, then tell the Parse CLI to perform the deploy. I use a bash script for this, but be warned: I’m not a bash expert by any means, so feedback is definitely appreciated.

# Compile the Ember app.
ember build -prod -o dist/public

# Copy Parse files and any Cloud Code.
cp .parse.project dist/.parse.project
cp .parse.local dist/.parse.local
cp -R cloud dist/cloud

# Deploy to Parse.
cd dist
parse deploy

# Clean up after we finish.
cd ..
rm -rf dist

Just run the bash script with sh your-file-name.sh, or maybe set it as an npm package script.

The Hard(-er) Way

At this point, you should have a fully-operational Ember app running on Parse…but we can do better. Let’s change our deploy process so we can use those history-based URLs.

Reset things. To start, change your app’s locationType parameter (in config/environment.js) back to 'auto'. No use in sticking with hash-based URLs anymore!

Make a mini node app. We’ll need to tell Parse to always serve up the root document (located at public/index.html) to any and all requests so that Ember can take over routing. Fortunately, this is easily accomplished by giving Parse a simple node app that it will run alongside serving static files. The following code is from Erik Hanchett‘s great blog post on serving Ember apps from Express:

var express = require('express');
var app = express();

app.get('*', function(req, res) {
// TODO: Send the contents of "public/index.html".
});

app.listen();

Parse starts by looking for files in the public directory before it will run this simple server, so we won’t need to worry about not handling routes for static assets like CSS and JavaScript. From the Parse documentation:

When a request goes to a URL of your subdomain, Parse will first look for a matching file in the public directory. If there is no match, then Parse will invoke any Express request handlers that you have registered in Cloud Code.

To tell Parse about this node server, have your app’s main.js file import it with require('cloud/app.js'); on the first line.

Send down the root file. Unfortunately, Parse’s node implementation lacks certain features (like Express’s Response object’s sendFile() function) that would make sending down a static file trivial. Instead, I’m using a bit of a hack: in the deploy script, make the root file an EJS template, then tell the node app to render it (something the Parse node binary can do):

var express = require('express');
var app = express();

app.set('views', 'cloud/views');
app.set('view engine', 'ejs');
app.use(express.bodyParser());

app.get('*', function(req, res) {
res.render('index');
});

app.listen();

Here’s the new part of the bash script that does that:

# Create the 'views' directory.
mkdir -p dist/cloud/views

# Copy the root file.
cp dist/public/index.html dist/cloud/views/index.html

# Rename it to '*.ejs'
mv dist/cloud/views/index.html dist/cloud/views/index.ejs

Deploy! Again! With the build script modified, we’re ready to deploy again! This time, our script will also copy the index.html file into the cloud/views folder so our mini-node app can serve it. All that’s left is to visit your Parse app and see if everything works correctly. If not…well, Parse has some great logging tools!


Even with the workaround for Ember’s history-based URLs, I’m thrilled with how easy it is to deploy an Ember CLI app to Parse. If you’re already a Parse user, or have been looking for an excuse to give it a try, why not try your hand at deploying an Ember app? If you run into any issues, or have suggestions for ways to streamline this process, share them in the comments!

Comments