fbpx

Blogs from the Ranch

< Back to Our Blog

Live Updates With Queues, WebSockets, and Push Notifications. Part 5: Deploying to Heroku

In this series we’ve built a React Native and Node app that receives and passes along notifications from services like GitHub and Netlify. It works great locally, but we can’t keep it running on our machine forever, and every time we restart ngrok the URL changes. We need to get our app deployed somewhere permanent.

If we weren’t using WebSockets, then functions-as-a-service would be a great option for deployment. But since WebSockets are stateful, you need to run a service like Amazon API Gateway in front of the functions to provide WebSocket statefulness, and setting that up can be tricky. Instead, we’ll deploy our app on Heroku, an easy hosting platform that allows us to continue to use WebSockets in the normal way.

If you like, you can download the completed server project and the completed client project for part 5.

Heroku

If you don’t already have a Heroku account, create one for free. (You may need to add a credit card, but it won’t be charged.) Then install and log in to the Heroku CLI.

Go into our node app’s directory in the terminal. If you aren’t already tracking your app in git for version control, initialize a git repo now:

$ git init .
$ echo node_modules > .gitignore

Heroku’s main functionality can be accessed either through the web interface or through the CLI. In this post we’ll be using both, depending on which is easiest for any given step.

To begin, create a new Heroku app for our backend using the CLI:

$ heroku create

This will create a new app and assign it a random name—in my case, murmuring-garden-42327. It will also add a git remote named heroku to your repo alongside any other remotes you may have. You can see this by running the following command:

$ git remote -v
heroku  https://git.heroku.com/murmuring-garden-42327.git (fetch)
heroku  https://git.heroku.com/murmuring-garden-42327.git (push)

We aren’t quite ready to deploy our app to Heroku yet, but we can go ahead and set up our database and queue services. We’ll do this step in the Heroku dashboard. Go to the dashboard, then click on your new app, then the “Resources” tab.

Under Add-ons, search for “mongo”, then click “mLab MongoDB”.

A modal will appear allowing you to choose a plan. The “Sandbox – Free” plan will work fine for us. Click “Provision.”

Next, search for “cloud,” then click “CloudAMQP.”

The default plan works here too: “Little Lemur – Free,” so click “Provision” again.

This has set up our database and queue server. How can we access them? The services provide URLs to our app via environment variables. To see them, click “Settings,” then “Reveal Config Vars.”

From the CLI, you can run heroku config to show the environment variables.

Here’s what they’re for:

  • CLOUDAMQP_APIKEY: we won’t need this for our tutorial app
  • CLOUDAMQP_URL: our RabbitMQ access URL
  • MONGODB_URI: our MongoDB access URL

Using Environment Variables

We need to update our application code to use these environment variables to access the backing services, but this raises a question: how can we set up analogous environment variables in our local environment? The dotenv library is a popular approach: it allows us to set up variables in a .env file in our app. Let’s refactor our app to use dotenv.

First, add dotenv as a dependency:

$ yarn add dotenv

Create two files, .env and .env.sample. It’s a good practice to not commit your .env file to version control; so far our connection strings don’t have any secure info, but later we’ll add a variable that does. But if you create and commit a .env.sample file with example data, this helps other users of your app see which environment variables your app uses. If you’re using git for version control, make sure .env is in your .gitignore file so it won’t be committed.

Add the following to .env.sample:

CLOUDAMQP_URL=fake_cloudamqp_url
MONGODB_URI=fake_mongodb_uri

This just documents for other users that these are the values needed.

Now let’s add the real values we’re using to .env:

CLOUDAMQP_URL=amqp://localhost
MONGODB_URI=mongodb://localhost:27017/notifier

Note that the name CLOUDAMQP_URL is a bit misleading because we aren’t using CloudAMQP locally, just a general RabbitMQ server. But since that’s the name of the environment variable CloudAMQP sets up for us on Heroku, it’ll be easiest for us to use the same one locally. And since CloudAMQP is giving us a free queue server, we shouldn’t begrudge them a little marketing!

The values we set in the .env file are the values from our lib/queue.js and lib/repo.js files respectively. Let’s replace the hard-coded values in those files with the environment variables. In lib/queue.js:

-const queueUrl = 'amqp://localhost';
+const queueUrl = process.env.CLOUDAMQP_URL;

And in lib/repo.js:

-const dbUrl = 'mongodb://localhost:27017/notifier';
+const dbUrl = process.env.MONGODB_URI;

Now, how can we load these environment variables? Add the following to the very top of both web/index.js and workers/index.js, even above any require() calls:

if (process.env.NODE_ENV !== 'production') {
  require('dotenv').config();
}

When we are not in the production environment, this will load dotenv and instruct it to load the configuration. When we are in the production environment, the environment variables will be provided by Heroku automatically, and we won’t have a .env file, so we don’t need dotenv to run.

To make sure we haven’t broken our app for running locally, stop any node processes you have running, then start node web and node workers, run the Expo app, and post a test webhook:

$ curl http://localhost:3000/webhooks/test -d "this is with envvars"

The message should show up in Expo as usual.

Configuring and Deploying

Heroku is smart enough to automatically detect that we have a node app and provision an appropriate environment. But we need to tell Heroku what processes to run. We do this by creating a Procfile at the root of our app and adding the following:

web: node web
worker: node workers

This tells Heroku that it should run two processes, web and worker, and tells it the command to run for each.

Now we’re finally ready to deploy. The simplest way to do this is via a git push. Make sure all your changes are committed to git:

$ git add .
$ git commit -m "preparing for heroku deploy"

Then push:

$ git push heroku master

This pushes our local master branch to the master branch on the heroku remote. When Heroku sees changes to its master branch, it triggers a deployment. We’ll be able to see the deployment process as it runs in the output of the git push command.

Deployment will take a minute or two due to provisioning the server and downloading dependencies. In the end, we’ll get a message like:

remote:        Released v7
remote:        https://murmuring-garden-42327.herokuapp.com/ deployed to Heroku

We have one more step to do. Heroku will start the process named web by default, but when we have any other processes we will need to start by ourselves. In our case, we need to start the worker process. We can do this a few different ways:

  • If you want to use the CLI, run heroku ps:scale worker=1. This scales the process named worker to run on a single “dyno” (kind of like the Heroku equivalent of a server)
  • If you want to use the web dashboard instead, go to your app, then to “Resources.” Next to the worker row, click the pencil icon to edit it, then set the slider to on, and click Confirm.

Testing

Now let’s update our Expo client app to point to our production servers. We could set up environment variables there as well, but for the sake of this tutorial let’s just change the URLs by hand. Make the following changes in MessageList.js, putting in your Heroku app name in place of mine:

 import React, { useState, useEffect, useCallback } from 'react';
-import { FlatList, Linking, Platform, View } from 'react-native';
+import { FlatList, Linking, View } from 'react-native';
 import { ListItem } from 'react-native-elements';
...
-const httpUrl = Platform.select({
-  ios: 'http://localhost:3000',
-  android: 'http://10.0.2.2:3000',
-});
-const wsUrl = Platform.select({
-  ios: 'ws://localhost:3000',
-  android: 'ws://10.0.2.2:3000',
-});
+const httpUrl = 'https://murmuring-garden-42327.herokuapp.com';
+const wsUrl = 'wss://murmuring-garden-42327.herokuapp.com';

Note that the WebSocket URL uses the wss protocol instead of ws; this is the secure protocol, which Heroku makes available for us.

Reload your Expo app. It should start out blank because our production server doesn’t have any data in it yet. Let’s send a test webhook, again substituting your app’s name for mine:

$ curl https://murmuring-garden-42327.herokuapp.com/webhooks/test -d "this is heroku"

You should see your message show up. We’ve got a real production server!

Next, let’s set up a GitHub webhook pointing to our Heroku server. In the testing GitHub repo you created, go to Settings > Webhooks. Add a new webhook and leave the existing one unchanged; that way you can continue receiving events on your development server as well.

  • Payload URL: your Heroku app URL, with /webhooks/github appended.
  • Content type: change this to application/json
  • Secret: leave blank
  • SSL verification: leave as “Enable SSL verification”
  • Which events would you like to trigger this webhook? Choose “Let me select individual events”

  • Scroll down and uncheck “Pushes” and anything else if it’s checked by default, and check “Pull requests.”
  • Active: leave this checked

Now head back to your test PR and toggle it open and closed a few times. You should see the messages show up in your Expo app.

Congratulations — now you have a webhooks-and-WebSockets app running in production!

Heroku Webhooks

Now that we have a Heroku app running, maybe we can set up webhooks for Heroku deployments as well!

Well, there’s one problem with that: it can be hard for an app that is being deployed to report on its deployment.

Surprisingly, if you set up a webhook for your Node app, you will get a message that the build started. It’s able to do this because Heroku leaves the existing app running until the build completes, then swaps out the running version. You won’t get a message that the build completed over the WebSocket, however—by that time the app has been restarted, your WebSocket connection is lost. The success message has been stored to the database, however, so if you reload the Expo app it will appear.

With that caveat in place — or if you have another Heroku app that you want to set up notifications for — here are a few pointers for how to do that.

To configure webhooks, open your site in the Heroku dashboard, then click “More > View webhooks.” Click “Create Webhook.” Choose the api:build event type: that will allow you to receive webhook events when builds both start and complete.

The webhook route itself should be very similar to the GitHub one. The following code can be used to construct a message from the request body:

const {
  data: {
    app: { name },
    status,
  },
} = req.body;

const message = {
  text: `Build ${status} for app ${name}`,
  url: null,
};

Note that the Heroku webhook doesn’t appear to send the URL of your app; if you want it to be clickable, you would need to use the Heroku Platform API to retrieve that info via another call.

What’s Next?

Now that we’ve gotten our application deployed to production, there’s one more piece we can add to make a fully-featured mobile app: push notifications to alert us in the background. To get push notifications, we’ll need to deploy our Expo app to a real hardware device.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project