# routeup.json

> The standalone config file for any project.

`routeup.json` works for any project and gives routeup a default route, targets,
and optional command. Node projects may instead embed the same routing settings
in [`package.json`](/docs/configuration/package-json-embed); when both exist,
`routeup.json` wins.

## Minimal config

```json title="routeup.json"
{
  "$schema": "https://raw.githubusercontent.com/mukul-mehta/routeup/main/routeup.schema.json",
  "name": "example-app",
  "port": 8080
}
```

With this present, run:

```bash
routeup serve
```

The configured name and port are used directly when no explicit name is passed.
An explicit name is always literal, so `routeup serve api` serves route `api`.

## Fields

```txt
$schema       JSON Schema URL for editor validation and completion
name          optional default route name
port          the local port to proxy to (optional when using command)
command       the shell command to run in script-runner mode
port_env_var  additional environment variable that receives the assigned port
targets       optional list of path-routed targets for one-origin frontend + API setups
expose        optional public exposure settings, including paths
capture       optional request/response capture settings for routeup inspect
```

The repository's [`routeup.schema.json`](https://github.com/mukul-mehta/routeup/blob/main/routeup.schema.json)
provides editor completion and catches unknown or invalid fields before routeup
runs.

The `name` field is a literal default used only when no positional name or
`ROUTEUP_NAME` is supplied. If it is omitted too, routeup uses the
working-directory basename. See [Name resolution](/docs/configuration/name-resolution).

## Running a command

When `command` is set and you run bare `routeup`, routeup starts your dev server
and manages its lifetime. It assigns a free port, injects it as `PORT`, and
routes to it automatically; you do not need to set `port` explicitly:

```json title="routeup.json"
{
  "name": "example-app",
  "command": "pnpm dev"
}
```

Frameworks that read `PORT` from the environment (Next.js, Nuxt, Express) work
with no further changes. Vite and Astro need one line of config to forward
`process.env.PORT` to `server.port`. See the
[Framework setup](/docs/configuration/frameworks) guide for per-framework
instructions.

If a process expects another variable, `port_env_var` receives the same assigned
port in addition to `PORT`:

```json title="routeup.json"
{
  "name": "webhook-consumer",
  "command": "go run ./cmd/dev",
  "port_env_var": "WEBHOOK_CONSUMER_PORT"
}
```

`PORT` remains set. `port_env_var` cannot replace Routeup-managed variables,
`HOST`, `PATH`, or `NODE_EXTRA_CA_CERTS`.

## Path routing

Use `targets` when one hostname should proxy different path prefixes to different local ports:

```json title="routeup.json"
{
  "name": "example-app",
  "targets": [
    { "path": "/", "port": 5173 },
    { "path": "/api", "port": 9080 }
  ]
}
```

This keeps the browser on one origin, for example `https://example-app.localhost/api/users`, while routeup sends `/api/*` to the API port.

## Config-driven exposure

Set `expose.enabled` when bare `routeup` or `routeup serve` should expose the
route without an explicit `--expose` flag:

```json title="routeup.json"
{
  "name": "example-app",
  "port": 8080,
  "expose": {
    "enabled": true
  }
}
```

## Path-limited exposure

By default, `--expose` opens every path on the route. Limit public exposure with `expose.paths`:

```json title="routeup.json"
{
  "name": "example-app",
  "port": 8080,
  "expose": {
    "paths": ["/api/webhooks/*"]
  }
}
```

The route still works locally at every path, but only matching paths are
reachable through the public URL. Other public requests receive an intentionally
ambiguous `404` response: `path does not exist or is not exposed`. Restricted
exposures also reject dot segments, backslashes, and encoded separators rather
than risk a different normalization in the upstream application.

## Request and response capture

routeup can retain request and response data so you can inspect it later with
`routeup inspect <request-id>`. Capture is off by default because retained data
may contain secrets.

```json title="routeup.json"
{
  "name": "example-app",
  "port": 8080,
  "capture": {
    "request": true,
    "response": true,
    "redact_headers": ["authorization", "cookie"]
  }
}
```

| Field | Description |
|---|---|
| `request` | Retain incoming request headers and body |
| `response` | Retain upstream response headers and body |
| `redact_headers` | Headers to omit from capture (case-insensitive); they are still forwarded to the upstream |

Each captured message is bounded to 256 KiB. If a body exceeds that,
`routeup inspect` shows a retained prefix and marks the capture as `partial`.

Get the request id from `routeup logs`, then inspect it:

```bash
routeup logs example-app --limit 1
```

```txt frame="terminal" title="terminal"
TIME      ROUTE                         SOURCE  STATUS  ID                    METHOD   DURATION  PATH
12:41:03  example-app                   public  200     req_Ap7kQ3mN8vR2xLzC  POST     38ms      /api/webhooks/github
```

```bash
routeup inspect req_Ap7kQ3mN8vR2xLzC
routeup inspect req_Ap7kQ3mN8vR2xLzC --curl
```

The curl form exports every retained header and the complete captured request
body to the local route. Configured redactions remain absent because they were
never stored.

## Discovery

routeup looks for config in the **current working directory only**: there is no
walk-up to parent directories. If both `routeup.json` and a `package.json`
`routeup` block exist in the same directory, `routeup.json` wins.

Per-language manifests beyond `package.json` (such as `pyproject.toml` or
`Cargo.toml`) are not read; non-JS projects use `routeup.json` directly.

Run [`routeup config`](/docs/cli-reference/config) to see the discovered source
and resolved route, targets, runner, exposure, capture, server, and token state.
