Skip to main content
routeup

Your local apps, by name.

https://example-app.localhost instead of localhost:3000. Stable names, trusted TLS, and a public URL when you need one. Point routeup at a port, or let it start your app.

$ brew install mukul-mehta/tap/routeup
  • Open source — MIT
  • Zero telemetry
  • Self-hostable server
  • Local routes work offline
$ routeup serve example-app --port 3000
route:   example-app
local:   https://example-app.localhost
targets:
  /        http://localhost:3000

press Ctrl-C to stop

Serve or run

Two ways to use it.

Point routeup at a port you already run, or let it start your app for you. Same route either way.

Your app keeps its server. routeup keeps the name.

Already running on a port? routeup serve example-app --port 3000 gives it a stable HTTPS name — no config file, no changes to your app.

Commit a routeup.json — or a routeup block in package.json — and bare routeup serve is enough. The route lives in the repo, so teammates get the same one.

Serve reference → Frontend + API recipe →
{
  "name": "example-app",
  "targets": [
    { "path": "/", "port": 5173 },
    { "path": "/api", "port": 8080 }
  ]
}
$ routeup serve
route:   example-app
local:   https://example-app.localhost
targets:
  /        http://localhost:5173
  /api     http://localhost:8080

press Ctrl-C to stop

Recipes

Pick a recipe.

Frontend and API behind one origin.

Route paths to different ports on one host. Longest prefix wins, so /api reaches the API and everything else reaches the app — same-origin cookies, no CORS config.

Frontend + API recipe →
{
  "name": "example-app",
  "targets": [
    { "path": "/",    "port": 5173 },
    { "path": "/api", "port": 8080 }
  ]
}
example-app.localhost/          localhost:5173
example-app.localhost/api/*      localhost:8080

Going public

Reachable from the internet, on demand.

Add --expose — or run routeup expose next to a route that's already up — and requests travel through a routeup server and back down an outbound tunnel to your machine. Your app still listens only on localhost.

Exposing publicly →

No token nothing to sign up for

$ routeup serve example-app --port 8080 --expose
route:   example-app
local:   https://example-app.localhost
public:  https://example-app.try.routeup.dev
expose:  all paths

An ephemeral URL under try.routeup.dev, live while the command runs. Good for a quick webhook or a link to your phone.

With a token your own namespace

$ routeup setup --token sk_routeup_…
$ routeup serve example-app --port 8080 --expose
public:  https://example-app.mukul.routeup.dev

Save a token once and every route gets a stable name under your namespace — reuse it in Stripe, an OAuth app, or a bookmark. Self-host the server and mint your own tokens too.

Getting started

From install to first route.

One binary and a one-time CA trust. Local routes need no account, no token, and no server contact.

Installation details →
  1. 01

    Install and set up.

    $ brew install mukul-mehta/tap/routeup
    $ routeup setup
    Public server URL for `expose` (leave empty for default, 'none' to stay local) [https://edge.routeup.dev]: 
    Token for https://edge.routeup.dev (blank to keep current): 
    certificate authority: created (~/.routeup/ca.crt)
    trusting the certificate system-wide (asks for your password)...
    certificate: trusted
    setting up port 443 (asks for your password)...
    port 443: ready
    server: saved (https://edge.routeup.dev)
    agent: started

    Not on Homebrew? There's an install script and release binaries — setup is the same either way.

  2. 02

    Serve something, then open it.

    $ routeup serve example-app --port 3000
    route:   example-app
    local:   https://example-app.localhost

    https://example-app.localhost

    Trusted TLS, no warning page. The name survives restarts and port changes.

Why routeup

What plain localhost can't do.

Four gaps that show up in almost every project.

  • [names]

    Names, not port numbers.

    You run three or four things at once. 3000, 8080, 5173 — the numbers don't say what they are, and they move when something grabs the port first. example-app.localhost doesn't.

  • [https]

    Real HTTPS, locally.

    Secure cookies, service workers, and WebAuthn only run over HTTPS. Plain http://localhost doesn't have it — every route gets a certificate your OS already trusts.

  • [public]

    A public URL that stays put.

    A Stripe webhook, a teammate, your phone on the same Wi-Fi — they can all reach one address that's the same every time you start up.

  • [origin]

    One origin for the whole stack.

    Put the frontend and API under the same host, and cookies, sessions, and CORS behave the way they will in production.

The same local target, two paths to reach it.

Local requestNo server contact
  1. Browserhttps://example-app.localhost
  2. Local agentterminates trusted TLS
  3. Your app127.0.0.1:3000
Public requestOnly when exposed
  1. Internet clienthttps://example-app.try.routeup.dev
  2. Routeup serverterminates public TLS
  3. Outbound tunnelencrypted back to your agent
  4. Your app127.0.0.1:3000

The boundaries

Local first, public by choice.

The CLI, the local agent, and the public server ship as one MIT-licensed binary. Local routes work offline with nothing to sign up for. Public exposure is opt-in, and the server can be the hosted one or your own.

  • Local means localNo account, token, DNS, or server request for *.localhost.
  • Capture is opt-inRequest bodies are not retained unless a route explicitly enables capture.
  • Ctrl-C cleans upStopping a command releases its route, child process, and tunnel with it.
  • Zero telemetryNo metrics, beacons, or crash reports. The binary talks to a server only when you expose a route.