Getting Started on Heroku with Node.js
Introduction
Complete this tutorial to deploy a sample Node.js app to Cedar, the legacy generation of the Heroku platform. To deploy the app to the Fir generation, only available to Heroku Private Spaces, follow this guide instead.
The tutorial assumes that you have:
- A verified Heroku Account
- An Eco dynos plan subscription (recommended)
- Node.js and npm installed locally
- Postgres installed locally
Using dynos and databases 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
Install the Heroku Command Line Interface (CLI). Use the CLI to manage and scale your app, provision add-ons, view your logs, and run your app locally.
The Heroku CLI requires Git, the popular version control system. If you don’t already have Git installed, complete the following before proceeding:
Download and run the installer for your platform:
Download the appropriate installer for your Windows installation:
You can find more installation options for the Heroku CLI here.
After installation, you can use the heroku command from your command shell.
To log in to the Heroku CLI, use the heroku login command:
$ heroku login
heroku: Press any key to open up the browser to login or q to exit:
Opening browser to https://cli-auth.heroku.com/auth/cli/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, click the Log In button on the page.
This authentication is required for the heroku and git commands to work correctly.
If you have any problems installing or using the Heroku CLI, see the main Heroku CLI article for advice and troubleshooting steps.
If you’re behind a firewall that uses a proxy to connect with external HTTP/HTTPS services, set the HTTP_PROXY or HTTPS_PROXY environment variables in your local development environment before running the heroku command.
Clone the Sample App
If you’re new to Heroku, it’s recommended that you complete this tutorial using the Heroku-provided sample application.
To deploy an existing application, follow this article instead.
Clone the sample application to get a local version of the code. Execute these commands in your local command shell or terminal:
$ git clone https://github.com/heroku/nodejs-getting-started.git
$ cd nodejs-getting-started
You now have a functioning Git repository that contains a simple application. It includes
a package.json file, which a Node.js dependency manager (like npm), can use to install
dependencies.
Create Your App
Using a dyno and a database to complete this tutorial counts towards your usage. Delete your app, and database as soon as you’re done to control costs.
Apps use Eco dynos if you’re subscribed to Eco by default. Otherwise, it defaults to Basic dynos. The Eco dynos plan is shared across all Eco dynos in your account. It’s 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 to prepare the platform to receive your source code:
$ heroku create
Creating app... done, tranquil-basin-07035
https://tranquil-basin-07035-a3c913bc59f0.herokuapp.com/ | https://git.heroku.com/tranquil-basin-07035.git
When you create an app, a Git remote named heroku is also created and added to your local repository configuration. Git remotes are versions of your repository that live on other servers. You can deploy your app by pushing code to that special Heroku-hosted remote associated with your app.
Heroku generates a random name for your app, in this case, tranquil-basin-07035. You can specify your own app name.
Deploy the App
Using a dyno to complete this tutorial counts towards your usage. Delete your app and database as soon as you’re done to control costs.
Deploy your code. This command pushes the main branch of the sample repo to your heroku remote, which then deploys to Heroku:
$ git push heroku main
remote: Updated 18 paths from 864d4da
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-24 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): 20.x || 22.x || 24.x
remote: engines.npm (package.json): unspecified (use default)
remote:
remote: Resolving node version 20.x || 22.x || 24.x...
remote: Downloading and installing node 24.15.0...
remote: Validating checksum
remote: Using default npm version: 11.12.1
remote:
remote: -----> Installing dependencies
remote: Installing node modules
remote:
remote: added 356 packages, and audited 357 packages in 3s
remote:
remote: 62 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote: npm notice
remote: npm notice New minor version of npm available! 11.12.1 -> 11.13.0
remote: npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.13.0
remote: npm notice To update run: npm install -g npm@11.13.0
remote: npm notice
remote:
remote: -----> Build
remote:
remote: -----> Caching build
remote: - npm cache
remote:
remote: -----> Pruning devDependencies
remote:
remote: removed 289 packages, and audited 68 packages in 634ms
remote:
remote: 18 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote: npm notice
remote: npm notice New minor version of npm available! 11.12.1 -> 11.13.0
remote: npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.13.0
remote: npm notice To update run: npm install -g npm@11.13.0
remote: npm notice
remote:
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 55M
remote: -----> Launching...
remote: Released v3
remote: https://tranquil-basin-07035-a3c913bc59f0.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/tranquil-basin-07035.git
* [new branch] main -> main
Visit the app at the URL shown in the logs. As a shortcut, you can also open the website as follows:
$ heroku open
Understanding the Procfile
Use a Procfile, a text file in the root directory of your application, to explicitly declare what command to execute to start your app.
The Procfile in the example app looks like this:
web: node index.js
This Procfile 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 is attached to
Heroku’s HTTP routing stack and receives web traffic when deployed. This command
uses the node executable to run the index.js file which starts the application.
A Procfile can contain additional process types. For example, you can declare a background worker process that processes items off a queue.
View Logs
Heroku treats logs as streams of time-ordered events, aggregated from the output streams of all your app and Heroku components. Heroku provides a single stream for all events.
View information about your running app by using one of the logging commands, heroku logs --tail:
$ heroku logs --tail
2026-05-05T22:03:30.991753+00:00 heroku[web.1]: Starting process with command `node index.js`
2026-05-05T22:03:31.948122+00:00 app[web.1]: Listening on 29612
2026-05-05T22:03:32.206823+00:00 heroku[web.1]: State changed from starting to up
2026-05-05T22:03:43.970625+00:00 app[web.1]: Rendering 'pages/index' for route '/'
2026-05-05T22:03:43.978072+00:00 heroku[router]: at=info method=GET path="/" host=tranquil-basin-07035-a3c913bc59f0.herokuapp.com request_id=7a47332d-ae59-95ae-152e-4505992ce577 fwd="123.456.789.0" dyno=web.1 connect=0ms service=11ms status=200 bytes=9109 protocol=http1.1 tls=false
To generate more log messages, refresh the app in your browser.
To stop streaming the logs, press Control+C.
Scale the App
After deploying the sample app, it automatically runs 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 by using the ps command:
$ heroku ps
=== web (Basic): node index.js (1)
web.1: up 2026/05/05 17:03:32 -0500 (~ 16s ago)
Scaling an app on Heroku is equivalent to changing the number of running dynos. Scale the number of web dynos to zero:
$ heroku ps:scale web=0
$ heroku ps:wait
Access the app again by hitting refresh in your browser, or heroku open to open it in a web tab. You get an error message because you no longer have web dynos available to serve requests.
Scale it up again:
$ heroku ps:scale web=1
$ heroku ps:wait
By default, apps use Eco dynos if you’re 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. Eco dynos sleep if they don’t receive any traffic for half an hour. This sleep behavior causes a few seconds delay for the first request upon waking. Eco dynos consume from a monthly, account-level quota of eco dyno hours. As long as you haven’t exhausted the quota, your apps can continue to run.
To avoid dyno sleeping, upgrade to a Basic or higher dyno type as described in the Dyno Types article. Upgrading to at least Standard dynos allows you to scale up to multiple dynos per process type.
Declare App Dependencies
Heroku recognizes an app as Node.js from 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 that looks like:
{
"name": "nodejs-getting-started",
"private": true,
"description": "A sample Node.js app using Express",
"engines": {
"node": "20.x || 22.x || 24.x"
},
...
"dependencies": {
"ejs": "^5.0.2",
"express": "^5.2.1"
},
...
The package.json file determines the version of Node.js to run your application on Heroku
and the dependencies to install with your application.
Run this command in your local directory to install the dependencies, preparing your system to run the app locally.
$ npm install
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated glob@7.2.3: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
npm warn deprecated glob@10.5.0: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
added 356 packages, and audited 357 packages in 2s
62 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
In your development environment, a package-lock.json file is generated or updated when npm install is run. It’s
important that changes to this lockfile are always committed to git before deploying your app since, if the dependencies
in the lockfile are out of sync with package.json, the application will fail to build.
Run the App Locally
Start your application locally using the heroku local command, which installed as part
of the Heroku CLI.
$ heroku local web --port 5006
[OKAY] Loaded ENV .env File as KEY=VALUE Format
(node:12179) [DEP0060] DeprecationWarning: The `util._extend` API is deprecated. Please use Object.assign() instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
5:03:51 PM web.1 | Listening on 5006
Just like Heroku, heroku local examines the Procfile to determine what to run.
Open http://localhost:5006 with your web browser to 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 learn how to propagate a local change to the application through to Heroku. As an example, lets modify the application to add a dependency and the code to use it.
Begin by adding a dependency for cool-ascii-faces in package.json. Run this command.
$ npm install cool-ascii-faces
added 11 packages, and audited 368 packages in 1s
62 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Modify index.js so that it requires this module at the start. Also add a new route (/cool) that uses it. You want the final code to look like:
const express = require('express')
const path = require('path')
const cool = require('cool-ascii-faces')
const port = process.env.PORT || 5006
const app = express()
app.use(express.static(path.join(__dirname, 'public')))
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')
app.get('/', (req, res) => {
console.log(`Rendering 'pages/index' for route '/'`)
res.render('pages/index')
})
app.get('/cool', (req, res) => {
console.log(`Rendering a cool ascii face for route '/cool'`)
res.send(cool())
})
const server = app.listen(port, () => {
console.log(`Listening on ${port}`)
})
// The number of seconds an idle Keep-Alive connection is kept open. This should be greater than the Heroku Router's
// Keep-Alive idle timeout of 90 seconds:
// - to ensure that the closing of idle connections is always initiated by the router and not the Node.js server
// - to prevent a race condition if the router sends a request to the app just as Node.js is closing the connection
// https://devcenter.heroku.com/articles/http-routing#keepalives
// https://nodejs.org/api/http.html#serverkeepalivetimeout
server.keepAliveTimeout = 95 * 1000
process.on('SIGTERM', async () => {
console.log('SIGTERM signal received: gracefully shutting down')
if (server) {
server.close(() => {
console.log('HTTP server closed')
})
}
})
Test locally.
When you visit your application at http://localhost:5001/cool,
the sample application shows you cute faces on each refresh: ( ⚆ _ ⚆ ).
Deploy. Almost every deploy to Heroku follows this same pattern. First, add the modified files to the local Git repository.
$ git add .
Commit the changes to the repository.
$ git commit -m "Add cool face API"
[main 558fdf8] Add cool face API
3 files changed, 99 insertions(+)
Deploy, just as you did previously.
$ git push heroku main
remote: Updated 18 paths from fbeb19c
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-24 stack
remote: -----> Using buildpack: heroku/nodejs
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): 20.x || 22.x || 24.x
remote: engines.npm (package.json): unspecified (use default)
remote:
remote: Resolving node version 20.x || 22.x || 24.x...
remote: Downloading and installing node 24.15.0...
remote: Validating checksum
remote: Using default npm version: 11.12.1
remote:
remote: -----> Restoring cache
remote: - npm cache
remote:
remote: -----> Installing dependencies
remote: Installing node modules
remote:
remote: added 367 packages, and audited 368 packages in 2s
remote:
remote: 62 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote:
remote: -----> Build
remote:
remote: -----> Caching build
remote: - npm cache
remote:
remote: -----> Pruning devDependencies
remote:
remote: removed 289 packages, and audited 79 packages in 629ms
remote:
remote: 18 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote: npm notice
remote: npm notice New minor version of npm available! 11.12.1 -> 11.13.0
remote: npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.13.0
remote: npm notice To update run: npm install -g npm@11.13.0
remote: npm notice
remote:
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 55M
remote: -----> Launching...
remote: Released v4
remote: https://tranquil-basin-07035-a3c913bc59f0.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/tranquil-basin-07035.git
af0f787..558fdf8 main -> main
Check that everything works.
$ heroku open cool
If everything is in working order, you see another face.
Provision a Logging Add-on
Beyond databases, add-ons provide many additional services for your application. In this step, you provision a free add-on to store your app’s logs.
By default, Heroku stores 1500 lines of logs from your application, but the full log stream is available as a service. Several add-on providers have logging services that provide things such as log persistence, search, and email and SMS alerts.
In this step, you provision one of these logging add-ons, Papertrail.
Provision the Papertrail logging add-on:
$ heroku addons:create papertrail
Creating papertrail on tranquil-basin-07035... free
Provisioning has been successful
Created papertrail-triangular-33307
Run heroku addons:docs papertrail to view documentation.
The add-on is now deployed and configured for your app. You can list add-ons for your app with this command:
$ heroku addons
Add-on Plan Price Max Price State
──────────────────────────────────────────────────────────────────────────────────
papertrail (papertrail-triangular-33307) choklad free free created
└─ as PAPERTRAIL
The table above shows add-ons and the attachments to the current app (tranquil-basin-07035) or other apps.
To see this particular add-on in action, visit your application’s Heroku URL a few times. Each visit generates more log messages, which get routed to the Papertrail add-on. Visit the Papertrail console to see the log messages:
$ heroku addons:open papertrail
Your browser opens up a Papertrail web console, showing the latest log events. The interface lets you search and set up alerts.
Start a Console
You can run a command, typically scripts and applications that are part of your app, in a one-off dyno using the heroku run command. You can also run an interactive bash session in your app’s environment:
$ heroku run bash
Running bash on tranquil-basin-07035... starting, run.6698
Running bash on tranquil-basin-07035... connecting, run.6698
Running bash on tranquil-basin-07035... up, run.6698
~ $ ls -lah
total 252K
drwx------ 8 u20774 dyno 4.0K May 5 22:04 .
drwxr-xr-x 11 root root 4.0K Apr 22 14:59 ..
-rw------- 1 u20774 dyno 226 May 5 22:03 .env
drwx------ 2 u20774 dyno 4.0K May 5 22:03 .github
-rw------- 1 u20774 dyno 139 May 5 22:03 .gitignore
drwx------ 4 u20774 dyno 4.0K May 5 22:04 .heroku
drwx------ 2 u20774 dyno 4.0K May 5 22:04 .profile.d
-rw------- 1 u20774 dyno 19 May 5 22:03 Procfile
-rw------- 1 u20774 dyno 3.0K May 5 22:03 README.md
-rw------- 1 u20774 dyno 242 May 5 22:03 app.json
-rw------- 1 u20774 dyno 1.4K May 5 22:03 index.js
drwx------ 92 u20774 dyno 4.0K May 5 22:04 node_modules
-rw------- 1 u20774 dyno 185K May 5 22:04 package-lock.json
-rw------- 1 u20774 dyno 598 May 5 22:03 package.json
drwx------ 3 u20774 dyno 4.0K May 5 22:03 public
-rw------- 1 u20774 dyno 1.6K May 5 22:03 test.js
drwx------ 4 u20774 dyno 4.0K May 5 22:03 views
~ $
~ $ exit
exit
If you receive an error, Error connecting to process, configure your firewall.
Type exit to exit the shell.
Define Config Vars
With Heroku, you can 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, lets modify index.js to introduce a new route, /times, that repeats an action depending on
the value of the TIMES environment variable. Copy the following into index.js:
const express = require('express')
const path = require('path')
const cool = require('cool-ascii-faces')
const port = process.env.PORT || 5006
const app = express()
app.use(express.static(path.join(__dirname, 'public')))
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')
app.get('/', (req, res) => {
console.log(`Rendering 'pages/index' for route '/'`)
res.render('pages/index')
})
app.get('/cool', (req, res) => {
console.log(`Rendering a cool ascii face for route '/cool'`)
res.send(cool())
})
app.get('/times', (req, res) => {
const times = process.env.TIMES || 5
console.log(`Rendering a count from 1 to ${times} for route '/times'`)
let result = ''
for (let i = 1; i <= times; i++) {
result += i + ' '
}
res.send(result)
})
const server = app.listen(port, () => {
console.log(`Listening on ${port}`)
})
// The number of seconds an idle Keep-Alive connection is kept open. This should be greater than the Heroku Router's
// Keep-Alive idle timeout of 90 seconds:
// - to ensure that the closing of idle connections is always initiated by the router and not the Node.js server
// - to prevent a race condition if the router sends a request to the app just as Node.js is closing the connection
// https://devcenter.heroku.com/articles/http-routing#keepalives
// https://nodejs.org/api/http.html#serverkeepalivetimeout
server.keepAliveTimeout = 95 * 1000
process.on('SIGTERM', async () => {
console.log('SIGTERM signal received: gracefully shutting down')
if (server) {
server.close(() => {
console.log('HTTP server closed')
})
}
})
heroku local automatically sets up the environment based on the contents of the .env file
in your local directory. In the top-level directory of your project, the .env file already
contains the following:
TIMES=2
If you run the app with heroku local --port 5006 and then open
http://localhost:5001/times, you see two numbers generated every time.
To set the config var on Heroku, execute this command.
$ heroku config:set TIMES=2
Setting TIMES and restarting tranquil-basin-07035... done, v5
TIMES: 2
View the config vars that are set using heroku config.
$ heroku config | grep TIMES
TIMES: 2
Add the modified files to the local Git repository.
$ git add .
Commit the changes to the repository.
$ git commit -m "Add /times route"
[main 93021ba] Add /times route
1 file changed, 10 insertions(+)
Deploy the changes to Heroku.
$ git push heroku main
remote: Updated 18 paths from 7fc0235
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-24 stack
remote: -----> Using buildpack: heroku/nodejs
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): 20.x || 22.x || 24.x
remote: engines.npm (package.json): unspecified (use default)
remote:
remote: Resolving node version 20.x || 22.x || 24.x...
remote: Downloading and installing node 24.15.0...
remote: Validating checksum
remote: Using default npm version: 11.12.1
remote:
remote: -----> Restoring cache
remote: - npm cache
remote:
remote: -----> Installing dependencies
remote: Installing node modules
remote:
remote: added 367 packages, and audited 368 packages in 2s
remote:
remote: 62 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote:
remote: -----> Build
remote:
remote: -----> Caching build
remote: - npm cache
remote:
remote: -----> Pruning devDependencies
remote:
remote: removed 289 packages, and audited 79 packages in 607ms
remote:
remote: 18 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote: npm notice
remote: npm notice New minor version of npm available! 11.12.1 -> 11.13.0
remote: npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.13.0
remote: npm notice To update run: npm install -g npm@11.13.0
remote: npm notice
remote:
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 55M
remote: -----> Launching...
remote: Released v6
remote: https://tranquil-basin-07035-a3c913bc59f0.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/tranquil-basin-07035.git
558fdf8..93021ba main -> main
Then visit it by running heroku open times.
Provision a Database
The sample app requires a database. Provision a Heroku Postgres database, an add-on available through the Elements Marketplace. Add-ons are cloud services that provide out-of-the-box additional services for your application, such as logging, monitoring, databases, and more.
An essential-0 Postgres size costs $5 a month, prorated to the minute. At the end of this tutorial, we prompt you to delete your database to minimize costs.
$ heroku addons:create heroku-postgresql:essential-0
Creating heroku-postgresql:essential-0 on tranquil-basin-07035... ~$0.007/hour (max $5/month)
Database should be available soon
postgresql-perpendicular-85328 is being created in the background. The app will restart when complete...
Run heroku addons:info postgresql-perpendicular-85328 to check creation progress.
Run heroku addons:docs heroku-postgresql to view documentation.
You can wait for the database to provision by running this command:
$ heroku pg:wait
After that command exits, your Heroku app can access the Postgres database. The DATABASE_URL environment variable stores your credentials, which your app is configured to connect to. You can see all the add-ons provisioned with the addons command:
$ heroku addons
Add-on Plan Price Max Price State
───────────────────────────────────────────────────────────────────────────────────────────────────────
heroku-postgresql (postgresql-perpendicular-85328) essential-0 ~$0.007/hour $5/month created
└─ as DATABASE
papertrail (papertrail-triangular-33307) choklad free free created
└─ as PAPERTRAIL
The table above shows add-ons and the attachments to the current app (tranquil-basin-07035) or other apps.
Use npm to add node-postgres to your dependencies.
$ npm install pg
added 13 packages, and audited 381 packages in 2s
62 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Edit your index.js file to use this module to connect to the database specified in
your DATABASE_URL environment variable. On line 4 of index.js, add:
const { Pool } = require('pg')
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false
}
})
Then, to execute a query against the database, add a new /db route that will return all
rows in the test_table table. On line 38 of index.js, add:
app.get('/db', async (req, res) => {
console.log(`Rendering the results of a database query for route '/db'`)
try {
const client = await pool.connect()
const result = await client.query('SELECT * FROM test_table')
res.render('pages/db', {
results: result ? result.rows : null
})
client.release()
} catch (err) {
console.error(err);
res.send("Error " + err);
}
})
Now that we’re connecting to a database, we also want to make sure to release our pool of connections when the app shuts down. After line 62, add the following to the shutdown handler:
pool.end().then(() => {
console.log('PG pool closed')
})
The index.js file should now look like this:
const express = require('express')
const path = require('path')
const cool = require('cool-ascii-faces')
const { Pool } = require('pg')
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false
}
})
const port = process.env.PORT || 5006
const app = express()
app.use(express.static(path.join(__dirname, 'public')))
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')
app.get('/', (req, res) => {
console.log(`Rendering 'pages/index' for route '/'`)
res.render('pages/index')
})
app.get('/cool', (req, res) => {
console.log(`Rendering a cool ascii face for route '/cool'`)
res.send(cool())
})
app.get('/times', (req, res) => {
const times = process.env.TIMES || 5
console.log(`Rendering a count from 1 to ${times} for route '/times'`)
let result = ''
for (let i = 1; i <= times; i++) {
result += i + ' '
}
res.send(result)
})
app.get('/db', async (req, res) => {
console.log(`Rendering the results of a database query for route '/db'`)
try {
const client = await pool.connect()
const result = await client.query('SELECT * FROM test_table')
res.render('pages/db', {
results: result ? result.rows : null
})
client.release()
} catch (err) {
console.error(err);
res.send("Error " + err);
}
})
const server = app.listen(port, () => {
console.log(`Listening on ${port}`)
})
// The number of seconds an idle Keep-Alive connection is kept open. This should be greater than the Heroku Router's
// Keep-Alive idle timeout of 90 seconds:
// - to ensure that the closing of idle connections is always initiated by the router and not the Node.js server
// - to prevent a race condition if the router sends a request to the app just as Node.js is closing the connection
// https://devcenter.heroku.com/articles/http-routing#keepalives
// https://nodejs.org/api/http.html#serverkeepalivetimeout
server.keepAliveTimeout = 95 * 1000
pool.end().then(() => {
console.log('PG pool closed')
})
process.on('SIGTERM', async () => {
console.log('SIGTERM signal received: gracefully shutting down')
if (server) {
server.close(() => {
console.log('HTTP server closed')
})
}
})
Add the modified files to the local Git repository.
$ git add .
Commit the changes to the repository.
$ git commit -m "Add /db route"
[main 7f8e625] Add /db route
3 files changed, 167 insertions(+), 2 deletions(-)
Deploy the changes to Heroku.
$ git push heroku main
remote: Updated 18 paths from b3b5fd7
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Building on the Heroku-24 stack
remote: -----> Using buildpack: heroku/nodejs
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): 20.x || 22.x || 24.x
remote: engines.npm (package.json): unspecified (use default)
remote:
remote: Resolving node version 20.x || 22.x || 24.x...
remote: Downloading and installing node 24.15.0...
remote: Validating checksum
remote: Using default npm version: 11.12.1
remote:
remote: -----> Restoring cache
remote: - npm cache
remote:
remote: -----> Installing dependencies
remote: Installing node modules
remote:
remote: added 380 packages, and audited 381 packages in 2s
remote:
remote: 62 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote:
remote: -----> Build
remote:
remote: -----> Caching build
remote: - npm cache
remote:
remote: -----> Pruning devDependencies
remote:
remote: removed 289 packages, and audited 92 packages in 613ms
remote:
remote: 18 packages are looking for funding
remote: run `npm fund` for details
remote:
remote: found 0 vulnerabilities
remote: npm notice
remote: npm notice New minor version of npm available! 11.12.1 -> 11.13.0
remote: npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.13.0
remote: npm notice To update run: npm install -g npm@11.13.0
remote: npm notice
remote:
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 55.1M
remote: -----> Launching...
remote: Released v8
remote: https://tranquil-basin-07035-a3c913bc59f0.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/tranquil-basin-07035.git
93021ba..7f8e625 main -> main
If you access /db, you receive an error because there’s 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 -c "create table test_table (id integer, name text); insert into test_table values (1, 'hello database');"
--> Connecting to postgresql-perpendicular-85328
CREATE TABLE
INSERT 0 1
Now when you access your app’s /db route with heroku open db, you see something like:

Read more about Heroku PostgreSQL.
You can use a similar technique to install MongoDB or Redis add-ons.
Delete Your App
Remove the app from your account. We only charge you for the resources you used.
This action permanently deletes your application and any add-ons attached to it.
$ heroku apps:destroy
You can confirm that your app is gone with this command:
$ heroku apps --all
Next Steps
You now know how to configure and deploy a Node.js app, view logs, and start a console.
To learn more, see: