# Incorporate free ngrok in your Node.js application for local development

**Date:** 2019-12-27  
**Author:** Kees C. Bakker  
**Categories:** Node.js  
**Original:** https://keestalkstech.com/incorporate-free-ngrok-in-your-node-js-application-for-local-development/

![Incorporate free ngrok in your Node.js application for local development](https://keestalkstech.com/wp-content/uploads/2019/12/photo-1508258006407-a26a3e44f2b4-scaled.jpg)

---

I come across [ngrok](https://ngrok.com/) more and more every day; it provides an excellent HTTP tunnel from my local computer to the internet. I use it to debug Slack apps locally, so I would like to integrate it with my Node.js application.

The free version generates a new sub domain every time you connect, that's why I'm usually start ngrok at the beginning of my day. I've tried to use the [ngrok npm package](https://www.npmjs.com/package/ngrok) in my application, but as the documentation says: *"The ngrok and all tunnels will be killed when node process is done."* I need the process to "survive" my application. Let's see what we can do about that...

[outline]

## Process

One picture says more than a 1000 words, so I created a flow-diagram to show what we are trying to do:

![](https://keestalkstech.com/wp-content/uploads/2020/02/ngrok-nodejs-auto-restart.svg)
*Diagram for starting NGROK in detached mode.*

We will start the ngrok process in [detached](https://nodejs.org/api/child_process.html#child_process_options_detached) mode, so it will survive the shutdown of our application. This is especially important, when you run a [nodemon](https://www.npmjs.com/package/nodemon) that restarts the application on every save.

## Dependencies

I'm using some packages to make this work:

```sh
npm install axios --save-dev
npm install fkill --save-dev
npm install ngrok --save-dev
```

The [*ngrok* package](https://www.npmjs.com/package/ngrok) ships the ngrok binary, which will launch the ngrok tunnel. The [*axios* package](https://www.npmjs.com/package/axios) is used to retrieve the ngrok URL. The [*fkill* package](https://www.npmjs.com/package/fkill) allows us to stop expired tunnels by killing the ngrok process.

## Configuration

Add a [ngrok configuration yaml file](https://ngrok.com/docs#tunnel-definitions) named `ngrok.yml` to the root of your application and fill it like this:

```yaml
tunnels:
  app:
    proto: http  # exposes an HTTP end point
    addr: 3000   # uses local port 3000
```

We also need to add an environment setting: `NGROK=true`. Production environments will not have this variable set, so ngrok is not started.

## The script

This file lives in my root directory and is called `ngrok.js`.

```js
// imports
const fs = require('fs');
const path = require('path');
const { platform } = require('os');
const { spawn } = require('child_process');

// settings
const pollInterval = 500;
const ngrokConfig = path.resolve('ngrok.yml');

// needed for spawning NGROK
let ngrokBin = '';
let ngrokDir = '';
let ngrokProc = '';
try {
    const ext = platform() === 'win32' ? '.exe' : '';
    ngrokDir = path.dirname(require.resolve('ngrok')) + '/bin';
    ngrokProc = 'ngrok' + ext;
    ngrokBin = ngrokDir + '/' + ngrokProc;
}
catch { }

async function ensureConnection(callback) {

  if (!ngrokEnabled) return false;

  if (!fs.existsSync(ngrokConfig)) {
    console.log(`Can't run ngrok - missing ${ngrokConfig}.`);
    return false;
  }

  if (ngrokBin == '') {
    console.log("Can't run ngrok - are dev dependencies installed?");
    return false;
  }

  console.log("Ensuring ngrok...");
  const url = await connect();
  if (url == null) return false;

  callback(url);
  return true;
}

async function connect() {
  let url = await getNgrokUrl();
  if (url) {
    console.log("ngrok already running.");
    return url;
  }

  console.log("Starting ngrok...");
  await startProcess();

  while (true) {
    url = await getNgrokUrl();
    if (url) return url;
    await delay(pollInterval);
  }
}

async function getNgrokUrl() {
    const axios = require('axios');
    const ping = 'http://127.0.0.1:4040/api/tunnels';
    let url = "";
    try {
        const response = await axios.get(ping);
        url = response.data.tunnels[0].public_url;
        if (url.startsWith("http://")) {
            url = "https://" + url.substr("http://".length);
        }
    }
    catch (ex) {
        return null;
    }
    try {
        await axios.get(url);
    }
    catch (ex) {
        if (ex && ex.response && ex.response.status == "402") {
            console.log("Killing expired tunnel...");
            stopProcess();
            await delay(2000);
            return null;
        }
    }
    return url;
}

function startProcess() {
  const start = ['start', '-config=' + ngrokConfig, "app"];
  const proc = spawn(ngrokBin, start, { cwd: ngrokDir, detached: true });
  proc.unref();
}

function stopProcess() {
    const fkill = require('fkill');
    fkill(ngrokProc, { force: true });
}

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

module.exports.ensureConnection = ensureConnection;
module.exports.getNgrokUrl = getNgrokUrl;
```

## Usage

Here is how I use it with a simple express server:

```js
const express = require('express');
const { ensureConnection } = require('./ngrok');
const port = process.env.PORT || 3000;

const app = express();
const server = createServer(app);
server.listen(port, async () => {
  console.log(`Listening on port ${server.address().port}`);
  await ensureConnection(url => {
    console.log(`Listerning to ${url}`);
  });
});
```

## Terminate ngrok background process

On Windows we can manually terminate ngrok like this:

```powershell
Taskkill /IM ngrok.exe /F
```

On Mac and Linux you can do:

```sh
sudo pkill ngrok
```

## Final thoughts

Ngrok is a wonderful tool to provide HTTP tunnels for local development. I'm looking forward to use it more and more in open source projects. A big shout-out to [Arno Jansen](https://www.linkedin.com/in/aetjansen/) for testing and debugging the code in our Slack bots.

## Improvements

2020-02-09: added ngrok kill command for Mac and Linux.
2020-02-08: only require dev packages when they are absolutely needed.
2020-02-08: environment variables might be loaded in a later stage, so only look for the NGROK variable when needed.
2020-02-08: expired tunnels will be killed using `fkill`.
