Expo.js apps using a localhost backend over ngrok
Published: 2021-01-17 18:26:53

The issue arises when creating React Native apps using Expo.js where you wish to have a local backend instead of a live url. Ngrok is used by many to accomplish this, however, if you are using a free account it can take some repeated effort to keep everything working

Free Ngrok urls are only available for 90 minutes, after which you need to restart it and get a fresh url

We can automate the process to make it as painless as possible

All files mentioned should be created in the project root beside `package.json`

Ensure axios and expo-constants are installed: `expo add axios expo-constants`

We will assume your local api is running on port 3333

First create a script called `local` in `package.json`:
```
  "scripts": {
    "start": "expo start",
    "local": "node ngrokhandler.js && EXPO_NGROK=1 expo start",
    ...
  }
```
Pass `EXPO_NGROK=1` to `expo start` that way `app.config.js` can choose to load ngrok or just ingore it

Once you've added all the pieces, start the app locally using `yarn run local`

create a file `ngrokhandler.js`

Note: if this is your only tunnel, then the local ngrok server is on port 4040, if you have more than one this number increments, so note it when you start ngrok. If you see a number greater than 4040, you may have ngrok running elsewhere

```
const fs = require('fs')
const axios = require('axios')

// ngrok starts at port 4040
// subsequent tunnels are numbered 4040 + n
axios.get('http://127.0.0.1:4040/api/tunnels')
.then(resp => {
  const httpsTunnel = resp.data.tunnels.find(t => t.proto==='https')
  const url = httpsTunnel.public_url
  fs.writeFileSync('./NGROK_URL', url)
})
.catch(e => {
  console.log('START NGROK in another window: ngrok http 3333')
  // exit err code so && in package json does not execute the expo start command
  process.exit(2)
})
```

This writes the current https tunnel url to a text file called `NGROK_URL`

Create or modify `app.config.js`:

```
const fs = require('fs')

// on production ngrokUrl will be null
// you don't need to have the file NGROK_URL
// in production

let ngrokUrl = null
if (process.env.EXPO_NGROK === '1') {
  ngrokUrl = fs.readFileSync('./NGROK_URL', { encoding: 'utf8'})
}

export default ({ config }) => {
  return {
    ...config,
    extra: {
      ...config.extra,
      ngrokUrl,
    }
  }
}
```
This brings in your `app.json` via the config param and adds the ngrok url to the extra object. Put anything else you want to add in extra

You can then access this via `expo-constants`. You probably don't want to use an ngrok url in production so we can turn it off by applying the `__DEV__` variable expo provides:

Example: `api.js`: 
```
import Constants from 'expo-constants'
const { ngrokUrl } = Constants.manifest.extra

const isLocal = ngrokUrl && __DEV__

const productionUrl = 'https://example.com'

const baseUrl = isLocal ? ngrokUrl : productionUrl

// now we can export an object of api endpoints
export default {
  login: `${baseUrl}/login`,
  etc: ...
}
```
Get your api running on port 3333.

Open another terminal and enter `ngrok http 3333`

Now you can run the app using `yarn run local` and the local url will be automatically added to the app. You have 90 minutes before you'll need to break the expo app and ngrok and re-run the commands (ctrl-c, up and enter in each terminal window)