Deploying Another Heroku App (With Heroku Postgres)
Tutorial
May 27, 2019 (updated: June 19, 2021)
The time has come! I’m deploying project GFT. Great opportunity to document the process of a Heroku deployment for a Node.js app with a Postgres database.
Heroku Postgres
In addition to using a Heroku dyno to host the app, I’m also using Heroku Postgres to host the database server. I’ve never used this before, and went back and forth between this option, hosting my own (i.e. Digital Ocean droplet), or using AWS RDS. My thinking was as follows:
Service | Pros | Cons |
---|---|---|
Heroku Postgres | Easy to set up. Easy integration with Postgres deployment. | With scale, it’s expensive compared to other options. |
DO Droplet | Fixed pricing of $5/month for the foreseeable future (sweetened by $100 credit I have). | Have to self-manage the server, updates, backups, security, rollback, etc. |
AWS RDS | Managed like Heroku Postgres but pricing is a lot lower. | Pricing is dependent on usage & could blow up unexpectedly (think bad actors). Also AWS is a deep web of confusion and time vacuums. |
In the end I decided to go with Heroku Postgres for the time being: they offer a free hobby version of the service which should be fine for this proof of concept. And if the need arises, I can take the time to learn about RDS and then migrate there.
But First…Create The Heroku Dyno
Turns out you can’t set up Heroku Postgres without having a dyno to link it to, so need to do that first:
- Sign up for Heroku if you don’t already have an account
- From the dashboard click ‘New’ and then ‘Create new app’
- Give the app a name and choose the region closest to you. The name will be the subdomain for the app on heroku, i.e.
myappname.herokuapp.com
- Click ‘Create app’
- The dyno is created and you’re spit out onto the ‘Deploy’ settings page. Since I’m deploying from a GitHub repo, I followed the steps to add the repo and trigger automatic deploys. You can also use the Heroku command line or a Docker container if you prefer.
Set Up The Database
I’m assuming we already have an app that’s been in dev mode locally until now, and we’ll just deploy the existing app but link it to the new Heroku Postgres database instead of using a local localhost
database. For my own app, there are some config files mentioned below which are referenced when I wrote about Sequelize CLI. My app also uses Auth0 for user authentication so that will need to be configured as well.
Open the ‘Resources’ settings tab. Under
Add-ons
start typing ‘postgres’ and the Heroku Postgres option will come up. Select it, and then a popup will pop up. With ‘Hobby Dev - Free’ selected in the dropdown menu, click theProvision
button. Now you’ll see the database is there underAdd-ons
. You can click it to open the Heroku Postgres dashboard and explore, but otherwise setup is done.Back in the app’s regular Heroku dashboard (not Heroku Postgres) click on the ‘Settings’ tab, then click the ‘Reveal Config Vars’ button. You’ll see that a
DATABASE_URL
variable has been created with your new Heroku Postgres database URI. This needs to be added into the application code so that it can connect to the new database in production. So…Back in the app, add the new environment variable to the db production settings:
// config/db-config.js
module.exports = {
"development": {
"username": process.env.PG_USER,
"password": process.env.PG_PASSWORD,
"database": process.env.PG_DATABASE,
"host": process.env.PG_HOST,
"port": process.env.PG_PORT,
"dialect": "postgres",
"operatorsAliases": false
},
"test": {
"username": process.env.PG_USER,
"password": process.env.PG_PASSWORD,
"database": process.env.PG_DATABASE,
"host": process.env.PG_HOST,
"port": process.env.PG_PORT,
"dialect": "postgres",
"operatorsAliases": false
},
"production": {
"use_env_variable": "DATABASE_URL",
"dialect": "postgres",
"operatorsAliases": false
}
}Update from the future: This post was written a while ago!…in Node 14+ the config above no longer works. I wrote about the fix here and it still works with Heroku as of June 2021.
Viewing The Data
Once everything is all linked, you can add the remote database to a local program to interact with the data outside of the application…I use Postico:
- In the Postico program, open the ‘Window’ menu and then ‘Favorites Window’ (or press ⌘ + N).
- Click the ‘New Favorite’ button and give it a nickname. I also recommend changing the color so that you’ll be able to easily know which database you’re working in.
- Copy & paste the database credentials from the Heroku Postgres dashboard (under ‘Settings’ then ‘Database Credentials’ then the ‘View credentials…’ button). Click ‘Connect’
Voila! If the app has been run, the database tables should have been created and you should see them there.
Final Configurations
- Back in the Heroku dashboard, add whatever environment variables the app uses…copy/paste them from the
.env
file, though you can skip anything Heroku will configure, like thePORT
orNODE_ENV
. - Check
package.json
to ensure thestart
script is one that Heroku will be able to run. For example if it includesnodemon
, make sure the package is installed in the app’s dependencies and not globally on your computer. Same for tools likeforever
,pm2
, etc.
…Except For Some Adjustments
The first time around I got an error because the app didn’t connect to a database. The solution was setting the production database configuration as written above, but here’s what the error looked like (timecodes removed) for reference. If it seems like your app hasn’t connected properly, look for an error like this and then troubleshoot it:
Unable to connect to the database: { SequelizeConnectionRefusedError: connect ECONNREFUSED 127.0.0.1:5432
at connection.connect.err (/app/node_modules/sequelize/lib/dialects/postgres/connection-manager.js:116:24)
at Connection.connectingErrorHandler (/app/node_modules/pg/lib/client.js:163:14)
at Connection.emit (events.js:189:13)
at Socket.reportStreamError (/app/node_modules/pg/lib/connection.js:71:10)
at Socket.emit (events.js:189:13)
at emitErrorNT (internal/streams/destroy.js:82:8)
at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
at process._tickCallback (internal/process/next_tick.js:63:19)
name: 'SequelizeConnectionRefusedError',
parent:
{ Error: connect ECONNREFUSED 127.0.0.1:5432
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1104:14)
errno: 'ECONNREFUSED',
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 5432 },
original:
{ Error: connect ECONNREFUSED 127.0.0.1:5432
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1104:14)
errno: 'ECONNREFUSED',
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 5432 } }Another error I got reminded me that I never installed a session store, which is required in a production environment. The step-by-step is below, but the error looked like this:
Warning: connect.session() MemoryStore is not
designed for a production environment, as it will leak
memory, and will not scale past a single process.Finally, because my app uses Auth0, I also needed to change some of the configurations in the Auth0 dashboard so that it will allow users to log in from the live production environment. More on that below.
Express Session Store
The package express-session
requires a session store for cookies and temporary application data. I wrote previously about using the session to store app data temporarily in addition to cookies. For development purposes you can get away with storing data in memory (express-session
can do this on its own), but it’s not set up for production, hence a session store is required.
The express-session
documentation provides a list of compatible session stores so pick the one that makes the most sense…in my case connect-session-sequelize
.
One thing to note: following these docs will require cookies to be set securely, as they should. And though Heroku apps are served over HTTPS, on a shared dyno it seems that Heroku actually terminates SSL before it reaches the individual application, and if the cookie can’t be set, Auth0 will not succeed in logging in. Actually technically it does succeed, but the app rejects the session and redirects back to /login
, which redirects back to the app, and back and forth until the browser shuts the whole thing down with ‘too many redirects’!! The solution I found was to include app.enable('trust proxy')
in the main app.js
file. Another option is to allow unsecure cookies…probably not a good idea though.
Auth0 Configuration
- Log into Auth0 to get to your dashboard.
- Click on ‘Applications’ in the sidebar and then click the gear icon (‘Settings’) for the relevant app.
- Scroll down to ‘Allowed Callback URLs’ and add your Heroku app URL with a
/callback
route, i.e.https://myappname.herokuapp.com/callback
. Note this list is comma separated. - Scroll down to ‘Allowed Web Origins’ and add the Heroku app URL.
- Scroll down to ‘Allowed Logout URLs’ and add the Heroku app URL.
- For all of these, I added both the
http
andhttps
versions of the domain to cover all bases. - Click ‘Save Changes’ at the bottom of the page.
- In the application code, update the
/logout
route to reflect the new URL. In my case, I changed this to an environment variable, added the variable to my.env
and.env.default
files, and added the new environment variable to my Heroku app settings. This way I can easily change the logout redirect URL again without updating application code. Another thing that turned out to be necessary in production was including the client ID in the redirect URL. Here’s what my logout controller looks like now:logoutAndRedirect(req, res) {
if (process.env.NODE_ENV === 'development') {
var auth0RedirectTarget = 'http%3A%2F%2Flocalhost%3A4000';
} else {
var auth0RedirectTarget = process.env.AUTH0_LOGOUT_URL;
}
req.logout();
res.status(200).redirect(`https://mtl.eu.auth0.com/v2/logout?client_id=${process.env.AUTH0_CLIENT_ID}&returnTo=${auth0RedirectTarget}`);
}, - In the application code (in my case, in
app.js
), you can also see that the authentication strategy uses an environment variable for a callback URL; this is what Auth0 uses to complete authentication. In the Heroku app settings, add thisAUTH0_CALLBACK_URL
variable with the valuehttps://myappname.herokuapp.com/callback
and save. - Note: for all of these URLs that go in Heroku environment variables, they need to be added without quotes.
Deploy!
- In the Heroku dashboard, open the ‘Deploy’ tab, and at the bottom under ‘Manual Deploy’ choose the branch you want to deploy from and click ‘Deploy Branch’. You’ll see the build logs come up and can watch it go.
- The deploy logs will go away when the build finishes, but you should check them for any errors. To do this, click ‘More’ in the top right corner of the Heroku dashboard and then click ‘View logs’.
- To see the app, click ‘Open app’ and if you see your app running and you can log in and out successfully, then celebrate! 💃💃💃