Services
Directories that Encore runs, tests and deploys as a unit
An Encore service is a directory with an encore.service.json file in it. Every Python file in that directory and its subdirectories belongs to the service. Encore runs each service as its own process, gives it its own tests, and grants it access to the infrastructure its code uses.
Services are declared in this file rather than in Python, so Encore.py has no service class or decorator. The service's code is an ordinary Python project with your own web framework.
Defining a service
A minimal service file names the service, sets its language, and tells Encore which HTTP paths the service serves:
encore.service.json{
"name": "orders",
"lang": "python",
"api": [
{ "type": "http", "prefix": "/", "name": "orders", "expose": true }
]
}
Next to it go the service's code, its dependencies and its lockfile:
my-app/
├── encore.app
└── orders/
├── encore.service.json
├── pyproject.toml
├── uv.lock
├── main.py
├── resources.py
└── migrations/
└── 1_create_orders.up.sql
A single-service app can also put encore.service.json at the app root, next to encore.app.
The file is parsed as strict JSON: no comments and no trailing commas.
The service file
Serving HTTP
Encore.py has no API framework, so a Python service usually declares a single http entry: a path prefix the service serves itself, with whatever framework you choose. Encore doesn't need to know the routes below the prefix; it forwards requests at or below it to the service.
"api": [
{ "type": "http", "prefix": "/", "name": "orders", "expose": true }
]
A service can list several entries, for example a public prefix and an internal one:
"api": [
{ "type": "http", "prefix": "/orders", "expose": true },
{ "type": "http", "prefix": "/internal", "name": "internal_orders" }
]
Instead of an http prefix, a service can describe what it serves with an OpenAPI document ({ "type": "openapi", "spec": "openapi.yaml" }), a GraphQL schema ({ "type": "graphql", "schema": "schema.graphql" }) or protobuf definitions ({ "type": "grpc", "protos": "proto" }). Paths are relative to the service directory and can't leave it.
Running and testing a service
run, test and build are each an object with a type. For a Python service they default to type "python", which has no options:
runstarts the service's entry point,main.py(orapp.py), through the package manager detected from the lockfile in the service directory:uv run main.pywith auv.lock,poetry run python main.pywith apoetry.lock, and so on. Without a lockfile it runspython3 main.py. See Installing the SDK for the full list.testrunspytestthe same way, for exampleuv run pytest.buildpackages the service's source tree together with its dependency manifests and lockfile, and recreates the environment with the same package manager on deployment.
Encore starts the command in the service's directory and passes the address to listen on in the ENCORE_LISTEN_ADDR environment variable, as host:port.
To use another command, set the type to custom and give the command as a list of arguments:
{
"name": "orders",
"lang": "python",
"run": { "type": "custom", "cmd": ["uv", "run", "python", "-m", "orders"] },
"test": { "type": "custom", "cmd": ["uv", "run", "pytest", "-x"] }
}
A custom run can also have a build command, run to completion each time before cmd starts. The command is run as given, not through a shell.
Multiple services
An app can have any number of services. Each one is a directory with its own encore.service.json, and, for Python, its own dependencies and lockfile, since Encore detects the package manager per service directory:
my-app/
├── encore.app
├── orders/
│ ├── encore.service.json
│ ├── pyproject.toml
│ ├── uv.lock
│ ├── main.py
│ └── db.py
└── billing/
├── encore.service.json
├── pyproject.toml
├── uv.lock
├── main.py
└── invoices.py
Services in one app don't have to share a language: a Python service can sit next to Go, TypeScript or Rust services, and use resources they declare. Start with one service and split it when there's a reason to; App Structure discusses where to draw the boundaries.
Which service owns what
Encore decides which service a file belongs to from its location: a file belongs to the service whose directory contains it. This is what Encore uses to grant access to infrastructure.
- A resource is declared by the service whose directory holds the declaration. For example, a
postgres.Database("orders", migrations="./migrations")inorders/db.pyis theordersservice's database, with migrations inorders/migrations. - Every use is attributed to the service whose directory contains the code using it, even if another service imports that file. If
billingimports a helper fromorders/that queries a database, the query is still recorded fororders. - Uses must be inside a service. Code outside every service directory, such as a shared library, can declare resources but can't use them directly. Have the service pass it a handle instead, for example
db.read()ordb.write()for a database.
To use another service's resource, reference it by name in your own service rather than importing the other service's code. For a database that's postgres.Database.named("orders"); buckets and topics have the same named method.
Service URLs
Your services call each other over HTTP with any client you like. To find another service's address, use base_url from encore.services:
billing/invoices.pyimport httpx
from encore.services import base_url
async def fetch_order(order_id: int) -> dict:
async with httpx.AsyncClient() as client:
response = await client.get(f"{base_url('orders')}/orders/{order_id}")
response.raise_for_status()
return response.json()
base_url returns the service's configured base URL with no trailing slash, so the request path goes right after it. It reads the runtime configuration and makes no network request. A service name the process doesn't know raises ValueError.
Looking up a service's URL makes the calling service depend on it, and Encore reads that dependency from your code. So:
- Pass the service name as a string literal:
base_url("orders"), notbase_url(name). - Call
base_urldirectly. Don't store it in a variable or pass the function around. - Call it from code inside a service.
- Name a service that exists in the app. Encore reports an unknown name when it parses the app.
A service looking up its own URL is allowed, and doesn't count as a dependency.