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:

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 for how services fit together in an app.

The service file

FieldMeaning
nameThe service's name, unique in the app. Required.
lang"rust". Required.
descriptionWhat the service is for, in a sentence or two. Optional.
apiWhat the service serves, and which gateways route to it. See Serving HTTP.
runHow encore run starts the service. Defaults to { "type": "rust" }.
buildHow encore build builds it. Defaults to { "type": "rust" }.
testHow encore test tests it. Defaults to { "type": "rust" }.
dockerHow encore build docker packages it: base_image and entrypoint.
workersIts background processes, which serve no requests. See 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.

KeyMeaning
prefixThe path the service serves, starting with /.
nameA 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 /.
exposetrue 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.
methodsThe 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 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:

CommandWhat Encore runs
encore runcargo build, then cargo run, with the SDK's local feature.
encore testcargo test, with the SDK's local feature.
encore buildcargo 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.

With the rust strategies, a service with a main process must build exactly one binary: that binary is the service. A service with 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.
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:

{ "name": "worker", "lang": "rust", "run": { "type": "custom", "cmd": ["cargo", "run", "--release", "--features", "encore-dev/local"] } }
KeyApplies toMeaning
cmdrun, build, testThe command and its arguments.
buildrunA command run to completion before cmd each time the service starts.
outputbuildThe 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:

Cargo.toml
[workspace] members = ["users", "billing", "libs/*"] resolver = "2" [workspace.dependencies] encore-dev = { path = "/path/to/encore-dev" }
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.
  • 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:

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 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.