# Connect to Tunnel Services

<EnterpriseFeature name="Secure tunneling" />

Once a tunnel exposes a service, your gateway calls it like any other backend —
with a URL. Tunnel services use the `service://` scheme instead of a hostname,
so a service named `payments-api` is reachable at `service://payments-api`.

This page covers the service configuration file, and the three places you use a
`service://` URL: handler code, route configuration, and environment variables.
To create a tunnel first, see [Set up a tunnel](./tunnel-setup.mdx).

## Service configuration reference

You define services in a JSON file and upload it with
[`zuplo tunnel services update`](../cli/tunnel-services-update.mdx). Each upload
replaces the tunnel's entire configuration.

```json title="tunnel-config.json"
{
  "version": 1,
  "services": [
    {
      "name": "payments-api-prod",
      "endpoint": "http://payments.internal:8080",
      "configurations": [
        {
          "project": "my-project",
          "accessibleBy": ["production"]
        },
        {
          "project": "my-other-project",
          "accessibleBy": ["production"]
        }
      ]
    },
    {
      "name": "payments-api-staging",
      "endpoint": "http://payments-staging.internal:8080",
      "configurations": [
        {
          "project": "my-project",
          "accessibleBy": ["preview", "working-copy"]
        }
      ]
    }
  ]
}
```

| Property                        | Type     | Description                                                                                                             |
| ------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `version`                       | number   | Configuration format version. Use `1`.                                                                                  |
| `services`                      | array    | The services this tunnel exposes.                                                                                       |
| `services[].name`               | string   | The service name your gateway calls, used as `service://<name>`.                                                        |
| `services[].endpoint`           | string   | The internal URL the tunnel forwards to, resolvable from the tunnel host. For example, `http://payments.internal:8080`. |
| `services[].configurations`     | array    | Which projects and environments can call this service. A project that isn't listed can't reach the service.             |
| `configurations[].project`      | string   | The name of the Zuplo project.                                                                                          |
| `configurations[].accessibleBy` | string[] | The environments allowed to call the service. Valid values are `production`, `preview`, and `working-copy`.             |

Use `accessibleBy` to keep environments apart. A service listed only under
`production` can't be reached from a preview build, even by a developer on your
own team.

To see the configuration a tunnel currently has, run
[`zuplo tunnel services describe`](../cli/tunnel-services-describe.mdx).

## Call a service from code

Pass the `service://` URL to `fetch` exactly as you would a public URL:

```ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  const response = await fetch("service://payments-api-prod/v1/charges");

  if (!response.ok) {
    context.log.error(`Payments API returned ${response.status}`);
    return new Response("Upstream error", { status: 502 });
  }

  return response;
}
```

Paths, query strings, headers, and request methods all work the way they do for
a normal `fetch`. The tunnel appends the path to the service's configured
`endpoint`.

## Call a service from route configuration

Any route property that accepts a URL accepts a `service://` URL, including the
[URL Rewrite handler](../handlers/url-rewrite.mdx):

```json title="config/routes.oas.json"
{
  "paths": {
    "/v1/charges": {
      "get": {
        "x-zuplo-route": {
          "handler": {
            "export": "urlRewriteHandler",
            "module": "$import(@zuplo/runtime)",
            "options": {
              "rewritePattern": "service://payments-api-prod/v1/charges"
            }
          }
        }
      }
    }
  }
}
```

You can also set the rewrite URL in the **Rewrite URL** field of the route
editor in the Zuplo Portal.

## Switch services per environment

Most teams run a separate internal service for each environment. Rather than
changing code or route configuration per environment, store the service URL in
an [environment variable](./environment-variables.mdx) and set a different value
in each Zuplo environment.

In your production environment, point the variable at the production service:

```text
TUNNEL_BASE_URL=service://payments-api-prod
```

In preview and working-copy environments, point it at staging:

```text
TUNNEL_BASE_URL=service://payments-api-staging
```

Read the variable in handler code:

```ts
import { ZuploContext, ZuploRequest, environment } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  return fetch(`${environment.TUNNEL_BASE_URL}/v1/charges`);
}
```

Environment variables work in route configuration too. The following URL Rewrite
handler combines the variable with a path parameter:

<Framed>

![The Request Handler pane of the Zuplo route editor, with Handler set to URL Rewrite and Rewrite URL set to a value that combines the TUNNEL_BASE_URL environment variable with a path parameter](../../public/media/tunnel-setup/16b93099-511d-435b-af85-167fab5814b2.png)

</Framed>

## Next steps

- [Advanced tunnel configuration](./tunnel-advanced.mdx) — the cloudflared
  foundation, environment variables, and self-managed images.
- [Troubleshoot a tunnel](./tunnel-troubleshooting.mdx) — diagnose requests that
  don't reach your backend.
