# Services

> Cargo packages Encore builds, runs and provisions for

A service in an Encore.rs app is a directory with an `encore.service.json` file and a Cargo package that builds the service's binary. The service's code is ordinary Rust: it serves HTTP with your own web framework, and declares and uses infrastructure through the SDK.

## Defining a service

Put an `encore.service.json` beside the service's `Cargo.toml`:

```json
-- users/encore.service.json --
{
  "name": "users",
  "lang": "rust",
  "description": "Accounts and sign-in.",
  "api": [
    { "type": "http", "prefix": "/users", "expose": true }
  ]
}
```

Every file in the directory, and in the directories below it, belongs to the service. Services can't be nested: a directory belongs to exactly one service.

On disk, an app with two Rust services might look like this:

```
/my-app
├── encore.app
├── users                     // users service
│   ├── encore.service.json
│   ├── Cargo.toml
│   ├── migrations
│   └── src
│       └── main.rs
└── billing                   // billing service
    ├── encore.service.json
    ├── Cargo.toml
    └── src
        └── main.rs
```

See [App Structure](/docs/app-structure) for how services fit together in an app.

## The service file

| Field | Meaning |
|---|---|
| `name` | The service's name, unique in the app. Required. |
| `lang` | `"rust"`. Required. |
| `description` | What the service is for, in a sentence or two. Optional. |
| `api` | What the service serves, and which gateways route to it. See [Serving HTTP](#serving-http). |
| `run` | How `encore run` starts the service. Defaults to `{ "type": "rust" }`. |
| `build` | How `encore build` builds it. Defaults to `{ "type": "rust" }`. |
| `test` | How `encore test` tests it. Defaults to `{ "type": "rust" }`. |
| `docker` | How `encore build docker` packages it: `base_image` and `entrypoint`. |
| `workers` | Its background processes, which serve no requests. See [Workers](/docs/rs/primitives/workers). |

### Serving HTTP

An `http` entry in `api` says the service serves everything at or below a path prefix itself. Encore doesn't need to know the routes under it, so the routing stays in your web framework.

| Key | Meaning |
|---|---|
| `prefix` | The path the service serves, starting with `/`. |
| `name` | A name for the entry, an identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to the prefix's last segment, so set it when the prefix is `/`. |
| `expose` | `true` to route requests from the default API gateway to the service, a list of gateway names to route from those, or `false` (the default) to keep it internal. |
| `methods` | The HTTP methods routed, such as `["GET", "POST"]`. Defaults to all of them. |

A service with no `api` entries serves no HTTP from the gateway. A service that runs only [workers](/docs/rs/primitives/workers) sets its `run` to `"none"`, and has no main process at all.

`api` also takes `openapi`, `graphql` and `grpc` entries, which describe what the service serves with a spec document.

## How Encore runs a Rust service

With the default `rust` strategies, Encore runs Cargo in the service's directory:

| Command | What Encore runs |
|---|---|
| `encore run` | `cargo build`, then `cargo run`, with the SDK's `local` feature. |
| `encore test` | `cargo test`, with the SDK's `local` feature. |
| `encore build` | `cargo build --release`, with the features of the clouds it builds for. |

Encore enables the features through the name your `Cargo.toml` gives the `encore-dev` dependency, so the package must depend on it directly (or through `workspace = true`). See [Cargo features](/docs/rs#cargo-features).

With the `rust` strategies, a service with a main process must build exactly one binary: that binary is the service. A service with [workers](/docs/rs/primitives/workers) and a main process also has one `[[bin]]` per worker that uses `entry`; the service's own binary is the one no worker names as its `entry`. A service with `run: "none"` has no service binary and needs only the binaries or commands used by its workers.

Under `encore run`, the service gets its address in two environment variables:

- `ENCORE_LISTEN_ADDR`, the `host:port` to listen on.
- `PORT`, the same port, for libraries that read only that.

```rust
fn listen_addr() -> String {
    std::env::var("ENCORE_LISTEN_ADDR").unwrap_or_else(|_| {
        let port = std::env::var("PORT").unwrap_or_else(|_| "8080".to_string());
        format!("0.0.0.0:{port}")
    })
}
```

When a service's code changes, `encore run` rebuilds and restarts it.

### Custom commands

A `custom` strategy runs a command of your choosing instead:

```json
{
  "name": "worker",
  "lang": "rust",
  "run": {
    "type": "custom",
    "cmd": ["cargo", "run", "--release", "--features", "encore-dev/local"]
  }
}
```

| Key | Applies to | Meaning |
|---|---|---|
| `cmd` | run, build, test | The command and its arguments. |
| `build` | run | A command run to completion before `cmd` each time the service starts. |
| `output` | build | The path of the built artifact, relative to the output directory. |

Encore adds no features to a custom command. Pass `--features encore-dev/local` (with the name you depend on the SDK by) in `run` and `test` commands, or buckets and Pub/Sub won't work locally. A service with a custom `build` must also set `docker.entrypoint` to be packaged as an image.

## Cargo workspaces

Each service is its own Cargo package, but the packages can share a Cargo workspace, for example with a `Cargo.toml` at the app root:

```toml
-- Cargo.toml --
[workspace]
members = ["users", "billing", "libs/*"]
resolver = "2"

[workspace.dependencies]
encore-dev = { path = "/path/to/encore-dev" }
```

```toml
-- users/Cargo.toml --
[package]
name = "users"
version = "0.1.0"
edition = "2024"

[dependencies]
encore-dev = { workspace = true }
shared = { path = "../libs/shared" }
```

A library crate a service depends on by path, directly or through the workspace, is part of that service's build: changing it rebuilds and restarts the services that use it.

## Which service uses what

Encore attributes everything to the service whose directory contains the code:

- **Usage.** Each call on a resource, such as `DB.query(…)` or `UPLOADS.upload(…)`, is recorded for the service the calling file belongs to, and that service is granted access to the resource. A call outside every service directory is an error, because there is no service to grant it to.
- **Handles.** Creating a handle, such as `DB.read()` or `UPLOADS.reference::<perms::Uploader>()`, is the usage. Library code outside the services takes the handle as an argument and records nothing itself. See [Databases](/docs/rs/primitives/databases#passing-the-database-to-other-code).
- **Ownership.** A few kinds of resource belong to the service that declares them: a Pub/Sub subscription is the declaring service's, a cron job requests a path on the declaring service, and a `Secret` or `Config` holds a value for the declaring service only. Databases, buckets, topics and Redis databases belong to the app, and any service may use them.

To use a resource that another service declares, declare a second `static` for it by name, such as `Database::named("users")`. This works across languages too.

## Calling other services

`encore::services::base_url` returns the base URL the calling service reaches another service at, without a trailing slash. Use it with any HTTP client:

```rust
async fn fetch_user(id: i64) -> Result<String, reqwest::Error> {
    let users = encore::services::base_url("users");
    reqwest::get(format!("{users}/users/{id}")).await?.text().await
}
```

Looking up a service's URL makes the calling service depend on it, and Encore reads that dependency from the code:

- The service name must be a string literal.
- The function must be called directly, not stored or passed around.
- The call must be inside a service.

`base_url` panics if the runtime knows no address for the service. That happens for a name that isn't a service of the app, and under `encore test`, which starts no other services.

## Static rules

Encore reads services and their resources from the source, so the declarations have a fixed shape:

- A resource is declared as a module-level `static`, with a `const` constructor such as `Database::new("users")`. A declaration inside a function, behind a condition, or wrapped in another call is an error.
- Resource names are string literals. Options are chained onto the constructor, each set once from a literal.
- A resource is used by calling its methods on the `static`. Assigning it to a variable or passing it to a function of your own is an error: create a handle instead. The SDK functions that take a resource, such as `Subscription::new(&TOPIC, …)` and the [testing](/docs/rs/develop/testing) helpers, are the exception.
- Every use of a resource is inside a service.

Encore reports these errors when it parses the app, on `encore run`, `encore test` or `encore build`.
