ยท hands on

Run Node.js apps on Heroku with TypeScript

This article provides a step-by-step guide on how to get started with Heroku, a polyglot platform. It covers topics such as setting up a Node.js environment, connecting a Git repository, writing a Node.js application, connecting a GitHub repository, getting logs, running CLI apps, and working with databases.

The best way to get started on the Heroku polyglot platform is to follow their fantastic introduction. It's also worth reading about their supported environments, deployment tasks and European deployment region. The second best advice is to follow our quick setup guide.

Contents

Getting Started on Heroku

To speed things up, I am providing a list of resources which I found useful when deploying my first Node.js web applications on Heroku.

Bootstrap Node.js environment

  1. Download the Heroku CLI (or Heroku Toolbelt)
  2. Run heroku --version to see if it works (I tested with v6.15.5)
  3. Run heroku login
  4. Run heroku whoami to see if you are logged in
  5. Run heroku create --region eu --buildpack heroku/nodejs to create a Node.js app on Heroku in a European data center (your app will get a URL like https://app-name-number.herokuapp.com/)
  6. Run heroku open -a app-name-number to see your application in a browser

Connect Git repository

By default, web applications created on Heroku (with heroku create) come with their own Git repository. If you are starting completely from scratch, then you can follow these steps to push your own code to Heroku's Git repository:

git init
git remote add origin https://git.heroku.com/app-name-number.git
npm init -y
git add .
git commit -m "Initial commit"
git push -u origin main

If you now execute heroku open, you will see an "Application error" because you need to specify a way to start a Node.js process (most likely through npm start).

Note: Heroku will also run npm run build by default before running npm start.

Write Node.js application

When npm init -y was executed, a package.json file has been created. We will make some adjustments to that file and create a web application based on the Express web framework:

  1. Run npm i --save-dev typescript
  2. Run npm i --save express @types/express
  3. Run npx tsc --init
  4. Modify source code to match the following files.
package.json
{
  "dependencies": {
    "@types/express": "4.16.1",
    "express": "4.16.4"
  },
  "devDependencies": {
    "typescript": "3.3.3333"
  },
  "engines": {
    "node": "11.x.x"
  },
  "license": "ISC",
  "main": "dist/main.js",
  "name": "my-app",
  "repository": {
    "type": "git",
    "url": "https://git.heroku.com/app-name-number.git"
  },
  "scripts": {
    "build": "tsc --build tsconfig.json",
    "start": "node dist/main.js"
  },
  "version": "1.0.0"
}
src/main.ts
import express from 'express';
 
const pkg = require('../package.json');
const app = express();
 
app.set('port', process.env.PORT || 3000);
 
app.all('*', (request, response) => {
  response.send(`<b>${pkg.name} v${pkg.version}</b>`);
});
 
app.listen(app.get('port'), () => {
  console.log(`Server is running on port "${app.get('port')}".`);
});

Deploy the latest code changes:

git add .
git commit -m "Show app version"
git push

Connect GitHub repository

To run deployments from your GitHub repository, you need to add Heroku's Git repository to your cloned GitHub repository:

# Add remote named "heroku"
git remote add heroku https://git.heroku.com/app-name-number.git
 
# Push to "main" branch on remote "heroku"
git push heroku main

You can also connect your Heroku application with code from GitHub by using Heroku's GitHub Deployments from the app dashboard.

Get logs

heroku logs --tail -a app-name-number

Run CLI apps

If you want to run a pure command-line app which does not serve a webpage, then you can change your use a "worker" dyno instead of a "web" done. Just place a file called Procfile in the root of your project and define the start script for the "worker" dyno:

worker: npm start

Tip: Prefer "npm start" over "node dist/main.js" to run in the npm context and to have access to environment variables like process.env.npm_package_name.

Next thing you should do is to scale down your "web" dyno, if you just want to run one "worker" dyno:

heroku ps:scale web=0 worker=1 -a app-name-number

Heroku runs health checks on the web domain of your application. That's why you need to scale down the "web" dyno if you just use a CLI app because otherweise the web health check will fail (see example below) and Heroku will kill your application:

2019-03-19T22:57:01.212233+00:00 heroku[router]: at=error code=H20 desc="App boot timeout" method=GET path="/" host=app-name-number.herokuapp.com request_id=7832
926b-a547-4927-9895-ea8a12e42765 fwd="91.10.153.93" dyno= connect= service= status=503 bytes= protocol=https
2019-03-19T22:57:49.960888+00:00 heroku[web.1]: State changed from starting to crashed
2019-03-19T22:57:49.863438+00:00 heroku[web.1]: Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch
2019-03-19T22:57:49.863489+00:00 heroku[web.1]: Stopping process with SIGKILL
2019-03-19T22:57:49.943431+00:00 heroku[web.1]: Process exited with status 137

Databases

Databases on Heroku are handled as "Add-ons". You can get a "Heroku Postgress" database for free. Connecting your Node.js application with it is super simple. Just add the database from your Heroku application dashboard as "Add-on" and Heroku will handle the rest for you and provide a process.env.DATABASE_URL variable that can be used to connect via object-relational mappers like TypeORM.

Note: You can also get the current connection properties (database name, user, password, port, etc.) from the settings panel of your data store on data.heroku.com but be aware that Heroku rotates credentials periodically so it's advisable to rely on the database connection url instead of a username and password combination.

initDatabase.ts
import 'reflect-metadata';
import { Connection, createConnection } from 'typeorm';
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions';
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
 
export default function initDatabase(): Promise<Connection> {
  const localhost: SqliteConnectionOptions = {
    database: 'test.db3',
    type: 'sqlite',
  };
 
  const production: PostgresConnectionOptions = {
    type: 'postgres',
    url: process.env.DATABASE_URL,
  };
 
  const connectionOptions = process.env.NODE_ENV === 'production' ? production : localhost;
 
  Object.assign(connectionOptions, {
    entities: ['src/entity/**/*.ts'],
    logging: false,
    migrations: ['src/migration/**/*.ts'],
    subscribers: ['src/subscriber/**/*.ts'],
    synchronize: true,
  });
 
  return createConnection(connectionOptions);
}

You will also need to have these dependencies in your package.json file:

"pg": "7.9.0",
"typeorm": "0.2.15",
"reflect-metadata": "0.1.10",
"sqlite3": "4.0.3"

Backups

Here is how you can make backups of your data store on Heroku:

  • You can find your databases on data.heroku.com
  • Use the following command to turn binary database backups into plain text: pg_restore backup.bin > backup.sql

If you look for a free tool to connection to your Heroku Postgres database, then have a look at pgAdmin.

Back to Blog