Horizon

Use a Horizon tunnel with Expo

Point an Expo or React Native app on a phone at a local API through a Horizon tunnel, with an EXPO_PUBLIC_ environment variable.

Call your local API from an Expo app running on a real phone, with an HTTPS URL that reaches your machine.

Before you begin

  • Node.js 18 or later
  • A Horizon account and the CLI (see Getting started)
  • A reserved subdomain for -s. Reserve one on the Subdomains page.
  • An Expo app, and a local API that listens on port 3000

How this differs from expo start --tunnel

The two tools tunnel different servers.

  • npx expo start --tunnel is for Expo's development server (Metro, port 8081). Expo says it uses ngrok and needs @expo/ngrok. It lets a device load your app bundle from anywhere.
  • A Horizon tunnel points at any local HTTP server. This page points it at your API, so the app can call that API from a phone.

You can use both: --tunnel to load the app, Horizon for the API. This page doesn't cover tunneling Metro with Horizon.

Start your API

Start your API on port 3000. Any HTTP server works.

Start a tunnel

Use -s with a subdomain you reserved. Reserved subdomains are a paid feature, see Pricing.

hrzn tunnel http://localhost:3000 -s my-api
Output
HORIZON: Tunnel connected
  URL          https://my-api.hrzn.run (reserved)
  Forwarding   http://localhost:3000
  Request log  https://hrzn.run/dashboard/tunnels/my-api

Keep this terminal open.

Point the app at the tunnel

Expo loads variables with the EXPO_PUBLIC_ prefix from .env files and inlines them into your JavaScript.

.env
EXPO_PUBLIC_API_URL=https://my-api.hrzn.run

Read it where you call the API.

lib/api.ts
const apiUrl = process.env.EXPO_PUBLIC_API_URL;

export async function getHealth() {
  const response = await fetch(`${apiUrl}/health`);
  return response.json();
}

Don't put secrets in EXPO_PUBLIC_ variables. Expo says they are visible in plain text in your compiled app. After you edit .env, do a full reload in Expo Go or your development build.

Check it works

Trigger the call in the app. The Horizon terminal prints a line for it:

Output
  GET     200  /health

Troubleshooting

Network request failed

React Native's fetch prints Network request failed when it can't reach the URL. Open https://my-api.hrzn.run/health in the phone's browser. If that fails, check that the Horizon terminal is running and that EXPO_PUBLIC_API_URL has the right subdomain.

The app still calls the old URL

Expo inlines EXPO_PUBLIC_ values into your code. Do a full reload in Expo Go or your development build after you change .env.

The URL changed after a restart

You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-api and the .env value stays valid.

Next steps

On this page