Getting Started on Heroku with Node.js
Last updated May 09, 2023
Table of Contents
Introduction
This tutorial will have you deploying a Node.js app to Heroku in minutes.
Hang on for a few more minutes to learn how to get the most out of the Heroku platform.
The tutorial assumes that you have:
- A verified Heroku Account
- Node.js and npm installed locally
- An Eco dynos plan subscription (recommended)
Using dynos to complete this tutorial counts towards your usage. We recommend using our low-cost plans to complete this tutorial. Eligible students can apply for platform credits through our new Heroku for GitHub Students program.
Set up
The Heroku CLI requires Git, the popular version control system. If you don’t already have Git installed, complete the following before proceeding:
In this step you’ll install the Heroku Command Line Interface (CLI). You use the CLI to manage and scale your applications, provision add-ons, view your application logs, and run your application locally.
Download and run the installer for your platform:
$ brew tap heroku/brew && brew install heroku
Download the appropriate installer for your Windows installation:
When installation completes, you can use the heroku
command from your terminal.
Use the heroku login
command to log in to the Heroku CLI:
$ heroku login
heroku: Press any key to open up the browser to login or q to exit
› Warning: If browser does not open, visit
› https://cli-auth.heroku.com/auth/browser/***
heroku: Waiting for login...
Logging in... done
Logged in as me@example.com
This command opens your web browser to the Heroku login page. If your browser is already logged in to Heroku, simply click the Log in
button displayed on the page.
This authentication is required for both the heroku
and git
commands to work correctly.
If you’re behind a firewall that requires use of a proxy to connect with external HTTP/HTTPS services, you can set the HTTP_PROXY
or HTTPS_PROXY
environment variables in your local development environment before running the heroku
command.
Before you continue, check that you have the prerequisites installed properly. Type each command below and make sure it displays the version you have installed. (Your versions might be different from the example.) If no version is returned, go back to the introduction of this tutorial and install the prerequisites.
All of the following local setup will be required to complete the “Declare app dependencies” and subsequent steps.
This tutorial will work for any version of Node greater than 10 - check that it’s there:
$ node --version
v18.14.2
npm
is installed with Node, so check that it’s there. If you don’t have it, install a more recent version of Node:
$ npm --version
9.5.0
Now check that you have git
installed. If not, install it and test again.
$ git --version
git version 2.39.2
Prepare the app
In this step, you will prepare a sample application that’s ready to be deployed to Heroku.
If you are new to Heroku, it is recommended that you complete this tutorial using the Heroku-provided sample application.
However, if you have your own existing application that you want to deploy instead, see this article to learn how to prepare it for Heroku deployment.
To clone a local version of the sample application that you can then deploy to Heroku, execute the following commands in your local command shell or terminal:
$ git clone https://github.com/heroku/node-js-getting-started.git
$ cd node-js-getting-started
You now have a functioning Git repository that contains a simple application as well as a package.json
file, which is used by Node’s dependency manager.
Deploy the app
In this step you will deploy the app to Heroku.
Using dynos to complete this tutorial counts towards your usage. Delete your app as soon as you are done to control costs.
By default, apps use Eco dynos if you are subscribed to Eco. Otherwise, it defaults to Basic dynos. The Eco dynos plan is shared across all Eco dynos in your account and is recommended if you plan on deploying many small apps to Heroku. Learn more here. Eligible students can apply for platform credits through our Heroku for GitHub Students program.
Create an app on Heroku, which prepares Heroku to receive your source code.
$ heroku create
Creating app... done, ⬢ shrouded-anchorage-35377
https://shrouded-anchorage-35377.herokuapp.com/ | https://git.heroku.com/shrouded-anchorage-35377.git
When you create an app, a git remote (called heroku
) is also created and associated with your local git repository.
Heroku generates a random name (in this case shrouded-anchorage-35377
) for your app, or you can pass a parameter to specify your own app name.
Now deploy your code:
$ git push heroku main
Enumerating objects: 554, done.
Counting objects: 100% (554/554), done.
Delta compression using up to 20 threads
Compressing objects: 100% (412/412), done.
Writing objects: 100% (554/554), 248.74 KiB | 124.37 MiB/s, done.
Total 554 (delta 109), reused 548 (delta 106), pack-reused 0
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-22 stack
remote: -----> Determining which buildpack to use for this app
remote: -----> Node.js app detected
remote:
remote: -----> Creating runtime environment
remote:
remote: NPM_CONFIG_LOGLEVEL=error
remote: NODE_VERBOSE=false
remote: NODE_ENV=production
remote: NODE_MODULES_CACHE=true
remote:
remote: -----> Installing binaries
remote: engines.node (package.json): 18.x || 16.x
remote: engines.npm (package.json): unspecified (use default)
remote:
remote: Resolving node version 18.x || 16.x...
remote: Downloading and installing node 18.14.2...
remote: Using default npm version: 9.5.0
remote:
remote: -----> Installing dependencies
remote: Installing node modules (package.json)
remote:
remote: added 160 packages, and audited 161 packages in 4s
remote:
remote: 64 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote:
remote: -----> Build
remote:
remote: -----> Caching build
remote: - node_modules
remote:
remote: -----> Pruning devDependencies
remote:
remote: up to date, audited 74 packages in 450ms
remote:
remote: 9 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote:
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 43.4M
remote: -----> Launching...
remote: Released v3
remote: https://shrouded-anchorage-35377.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/shrouded-anchorage-35377.git
* [new branch] main -> main
The application is now deployed. Ensure that at least one instance of the app is running:
$ heroku ps:scale web=1
Now visit the app at the URL generated by its app name. As a handy shortcut, you can open the website as follows:
$ heroku open
View logs
Heroku treats logs as streams of time-ordered events aggregated from the output streams of all your app and Heroku components, providing a single channel for all of the events.
View information about your running app using one of the logging commands, heroku logs --tail
:
$ heroku logs --tail
2023-03-02T19:56:09.671017+00:00 heroku[web.1]: Starting process with command `npm start`
2023-03-02T19:56:12.617099+00:00 app[web.1]:
2023-03-02T19:56:12.617126+00:00 app[web.1]: > node-js-getting-started@0.3.0 start
2023-03-02T19:56:12.617127+00:00 app[web.1]: > node index.js
2023-03-02T19:56:12.617127+00:00 app[web.1]:
2023-03-02T19:56:12.738203+00:00 app[web.1]: Listening on 16832
2023-03-02T19:56:13.217147+00:00 heroku[web.1]: State changed from starting to up
Visit your application in the browser again, and you’ll see another log message generated.
Press Control+C
to stop streaming the logs.
Define a Procfile
Use a Procfile, a text file in the root directory of your application, to explicitly declare what command should be executed to start your app.
The Procfile
in the example app you deployed looks like this:
web: npm start
This declares a single process type, web
, and the command needed to run it. The name web
is important here. It declares that this process type will be attached to the HTTP routing stack of Heroku, and receive web traffic when deployed. This command will use the start
script that is specified in the package.json
.
Procfiles can contain additional process types. For example, you might declare one for a background worker process that processes items off of a queue.
Scale the app
Right now, your app is running on a single web dyno. Think of a dyno as a lightweight container that runs the command specified in the Procfile
.
You can check how many dynos are running using the ps
command:
$ heroku ps
=== web (Eco): npm start (1)
web.1: up 2023/03/02 15:56:13 -0400 (~ 6m ago)
By default, your app is deployed on an eco dyno. Eco dynos will sleep after a half hour of inactivity (if they don’t receive any traffic). This causes a delay of a few seconds for the first request upon waking. Subsequent requests will perform normally. Eco dynos also consume from a monthly, account-level quota of eco dyno hours - as long as the quota is not exhausted, all eco apps can continue to run.
To avoid dyno sleeping, you can upgrade to a Basic or Professional dyno type as described in the Dyno Types article. For example, if you migrate your app to a professional dyno, you can easily scale it by running a command telling Heroku to execute a specific number of dynos, each running your web process type.
Scaling an application on Heroku is equivalent to changing the number of dynos that are running. Scale the number of web dynos to zero:
$ heroku ps:scale web=0
Access the app again by hitting refresh on the web tab, or heroku open
to open it in a web tab. You will get an error message because you no longer have any web dynos available to serve requests.
Scale it up again:
$ heroku ps:scale web=1
Declare app dependencies
Heroku recognizes an app as Node.js by the existence of a package.json
file in the root directory. For your own apps, you can create one by running npm init --yes
.
The demo app you deployed already has a package.json
, and it looks something like this:
{
"name": "node-js-getting-started",
"version": "0.3.0",
...
"engines": {
"node": "18.x || 16.x"
},
"dependencies": {
"ejs": "^3.1.5",
"express": "^4.15.2"
},
...
}
The package.json
file determines both the version of Node.js that will be used to run your application on Heroku, as well as the dependencies that should be installed with your application.
Run this command in your local directory to install the dependencies, preparing your system for running the app locally:
$ npm install
added 160 packages in 6s
Once dependencies are installed, you will be ready to run your app locally. You’ll notice that a package-lock.json
file is generated when npm install
is run. Make sure to check this into git. When subsequent dependencies are added, npm will make changes to this file, so be sure to add those changes to git too.
When an app is deployed, Heroku reads the package.json
to install the appropriate node version and the package-lock.json
to install the dependencies.
Run the app locally
Now start your application locally using the heroku local
command, which was installed as part of the Heroku CLI:
$ heroku local web --port 5001
[OKAY] Loaded ENV .env File as KEY=VALUE Format
4:09:49 p.m. web.1 | > node-js-getting-started@0.3.0 start
4:09:49 p.m. web.1 | > node index.js
4:09:49 p.m. web.1 | Listening on 5001
Just like Heroku, heroku local
examines the Procfile
to determine what to run.
Open http://localhost:5001 with your web browser. You should see your app running locally.
To stop the app from running locally, in the CLI, press Ctrl
+C
to exit.
Push local changes
In this step you’ll learn how to propagate a local change to the application through to Heroku. As an example, you’ll modify the application to add an additional dependency and the code to use it.
Begin by adding a dependency for cool-ascii-faces
in package.json
. Run the following command to do this:
$ npm install cool-ascii-faces
added 11 packages in 2s
Modify index.js
so that it requires
this module at the start. Also add a new route (/cool
) that uses it. Your final code should look like this:
const cool = require('cool-ascii-faces')
const express = require('express')
const path = require('path')
const PORT = process.env.PORT || 5001
express()
.use(express.static(path.join(__dirname, 'public')))
.set('views', path.join(__dirname, 'views'))
.set('view engine', 'ejs')
.get('/', (req, res) => res.render('pages/index'))
.get('/cool', (req, res) => res.send(cool()))
.listen(PORT, () => console.log(`Listening on ${ PORT }`))
Now test locally:
$ npm install
$ heroku local --port 5001
Visiting your application at http://localhost:5001/cool, you should see cute faces displayed on each refresh: ( ⚆ _ ⚆ )
.
Now deploy. Almost every deploy to Heroku follows this same pattern. First, add the modified files to the local git repository:
$ git add .
Now commit the changes to the repository:
$ git commit -m "Add cool face API"
Now deploy, just as you did previously:
$ git push heroku main
Finally, check that everything is working:
$ heroku open cool
You should see another face.
Provision add-ons
Add-ons are third-party cloud services that provide out-of-the-box additional services for your application, from persistence through logging to monitoring and more.
By default, Heroku stores 1500 lines of logs from your application. However, it makes the full log stream available as a service - and several add-on providers have written logging services that provide things such as log persistence, search, and email and SMS alerts.
In this step you will provision one of these logging add-ons, Papertrail.
Provision the papertrail logging add-on:
$ heroku addons:create papertrail
Creating papertrail on ⬢ shrouded-anchorage-35377... free
Welcome to Papertrail. Questions and ideas are welcome (technicalsupport@solarwinds.com). Happy logging!
Created papertrail-fluffy-46630 as PAPERTRAIL_API_TOKEN
Use heroku addons:docs papertrail to view documentation
The add-on is now deployed and configured for your application. You can list add-ons for your app like so:
$ heroku addons
To see this particular add-on in action, visit your application’s Heroku URL a few times. Each visit will generate more log messages, which should now get routed to the papertrail add-on. Visit the papertrail console to see the log messages:
$ heroku addons:open papertrail
Your browser will open up a Papertrail web console, showing the latest log events. The interface lets you search and set up alerts:
Start a console
To get a real feel for how dynos work, you can create another one-off dyno and run the bash
command, which opens up a shell on that dyno. You can then execute commands there. Each dyno has its own ephemeral filespace, populated with your app and its dependencies - once the command completes (in this case, bash
), the dyno is removed.
$ heroku run bash
Running bash on ⬢ shrouded-anchorage-35377... up, run.1662 (Eco)
~ $ ls
Procfile README.md app.json index.js node_modules package-lock.json package.json public test.js views
~ $ exit
exit
If you receive an error, Error connecting to process
, then you may need to configure your firewall.
Don’t forget to type exit
to exit the shell and terminate the dyno.
Define config vars
Heroku lets you externalize configuration - storing data such as encryption keys or external resource addresses in config vars.
At runtime, config vars are exposed as environment variables to the application. For example, modify index.js
so that it introduces a new route, /times
, that repeats an action depending on the value of the TIMES
environment variable. Under the existing get()
call, add another:
.get('/times', (req, res) => res.send(showTimes()))
At the end of the file, add the following definition for the new function, showTimes()
:
function showTimes() {
const times = process.env.TIMES || 5
let result = ''
for (i = 0; i < times; i++) {
result += i + ' '
}
return result
}
heroku local
will automatically set up the environment based on the contents of the .env
file in your local directory. In the top-level directory of your project, there is already a .env
file that has the following contents:
TIMES=2
If you run the app with heroku local --port 5001
and then open http://localhost:5001/times, you’ll see two numbers will be generated every time.
To set the config var on Heroku, execute the following:
$ heroku config:set TIMES=2
View the config vars that are set using heroku config
:
$ heroku config
=== shrouded-anchorage-35377 Config Vars
PAPERTRAIL_API_TOKEN: w9EnziexBT2mzMDtS4M
TIMES: 2
Deploy your changed application to Heroku and then visit it by running heroku open times
.
Provision a database
Adding a database to complete this tutorial counts towards your usage. Delete your database as soon as you’re done to control costs. Learn about our low-cost plans. Eligible students can apply for platform credits through our Heroku for GitHub Students program.
The add-on marketplace has a large number of data stores, from Redis and MongoDB providers, to Postgres and MySQL. In this step, you will add a Heroku Mini Postgres Starter Tier dev database to your app.
Add the database:
$ heroku addons:create heroku-postgresql:mini
Creating heroku-postgresql:mini on ⬢ shrouded-anchorage-35377... $5/month
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-asymmetrical-00466 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
This creates a database, and sets a DATABASE_URL
environment variable (you can check by running heroku config
).
Use npm
to add node-postgres to your dependencies:
$ npm install pg
added 14 packages in 2s
"dependencies": {
"cool-ascii-faces": "^1.3.4",
"ejs": "^3.1.5",
"express": "^4.15.2",
"pg": "^8.9.0"
},
Now edit your index.js
file to use this module to connect to the database specified in your DATABASE_URL
environment variable. Add this near the top:
const { Pool } = require('pg')
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false
}
})
Now add another route, /db
, by adding the following just after the existing .get('/', ...)
:
.get('/db', async (req, res) => {
try {
const client = await pool.connect();
const result = await client.query('SELECT * FROM test_table');
const results = { 'results': (result) ? result.rows : null};
res.render('pages/db', results );
client.release();
} catch (err) {
console.error(err);
res.send("Error " + err);
}
})
This ensures that when you access your app using the /db
route, it will return all rows in the test_table
table.
Deploy this to Heroku. If you access /db
you will receive an error as there is no table in the database. Assuming that you have Postgres installed locally, use the heroku pg:psql
command to connect to the remote database, create a table and insert a row:
$ heroku pg:psql
--> Connecting to postgresql-asymmetrical-00466
psql (14.7 (Homebrew))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
shrouded-anchorage-35377::DATABASE=> create table test_table (id integer, name text);
CREATE TABLE
shrouded-anchorage-35377::DATABASE=> insert into test_table values (1, 'hello database');
INSERT 0 1
shrouded-anchorage-35377::DATABASE=> \q
Now when you access your app’s /db
route with heroku open db
, you will see something like this:
Read more about Heroku PostgreSQL.
A similar technique can be used to install MongoDB or Redis add-ons.
Next steps
You now know how to deploy an app, change its configuration, view logs, scale, and attach add-ons.
Here’s some recommended reading. The first, an article, will give you a firmer understanding of the basics. The second is a pointer to the main Node.js category here on Dev Center:
- Read How Heroku Works for a technical overview of the concepts you’ll encounter while writing, configuring, deploying and running applications.
- Visit the Node.js category to learn more about developing and deploying Node.js applications.
- Read Deploying Node.js Apps on Heroku to understand how to take an existing Node.js app and deploy it to Heroku.