
encore-service
โ 25by encoredev ยท part of encoredev/skills
Plan how to split an Encore.ts application into services and lay out its directory structure. Architecture and decomposition, not first-time CLI install (that's `encore-getting-started`).
This is the playbook your agent receives when the skill activates โ you don't need to read it to use the skill, but it's here to audit before installing.
Encore Service Structure
Instructions
Creating a Service
Every Encore service needs an encore.service.ts file:
// encore.service.ts
import { Service } from "encore.dev/service";
export default new Service("my-service");Minimal Service Structure
my-service/
โโโ encore.service.ts # Service definition (required)
โโโ api.ts # API endpoints
โโโ db.ts # Database (if needed)Application Patterns
Single Service (Recommended Start)
Best for new projects - start simple, split later if needed:
my-app/
โโโ package.json
โโโ encore.app
โโโ encore.service.ts
โโโ api.ts
โโโ db.ts
โโโ migrations/
โโโ 001_initial.up.sqlMulti-Service
For distributed systems with clear domain boundaries:
my-app/
โโโ encore.app
โโโ package.json
โโโ user/
โ โโโ encore.service.ts
โ โโโ api.ts
โ โโโ db.ts
โโโ order/
โ โโโ encore.service.ts
โ โโโ api.ts
โ โโโ db.ts
โโโ notification/
โโโ encore.service.ts
โโโ api.tsLarge Application (System-based)
Group related services into systems:
my-app/
โโโ encore.app
โโโ commerce/
โ โโโ order/
โ โ โโโ encore.service.ts
โ โโโ cart/
โ โ โโโ encore.service.ts
โ โโโ payment/
โ โโโ encore.service.ts
โโโ identity/
โ โโโ user/
โ โ โโโ encore.service.ts
โ โโโ auth/
โ โโโ encore.service.ts
โโโ comms/
โโโ email/
โ โโโ encore.service.ts
โโโ push/
โโโ encore.service.tsService-to-Service Calls
Import other services from ~encore/clients:
import { user } from "~encore/clients";
export const getOrderWithUser = api(
{ method: "GET", path: "/orders/:id", expose: true },
async ({ id }): Promise<OrderWithUser> => {
const order = await getOrder(id);
const orderUser = await user.get({ id: order.userId });
return { ...order, user: orderUser };
}
);When to Split Services
Split when you have:
| Signal | Action |
|---|---|
| Different scaling needs | Split (e.g., auth vs analytics) |
| Different deployment cycles | Split |
| Clear domain boundaries | Split |
| Shared database tables | Keep together |
| Tightly coupled logic | Keep together |
| Just organizing code | Use folders, not services |
Service with Middleware
import { Service } from "encore.dev/service";
import { middleware } from "encore.dev/api";
const loggingMiddleware = middleware(
{ target: { all: true } },
async (req, next) => {
console.log(`Request: ${req.requestMeta?.path}`);
return next(req);
}
);
export default new Service("my-service", {
middlewares: [loggingMiddleware],
});Middleware Targeting
Control which endpoints middleware applies to:
// Apply to all endpoints
middleware({ target: { all: true } }, handler);
// Apply only to authenticated endpoints
middleware({ target: { auth: true } }, handler);
// Apply only to exposed (public) endpoints
middleware({ target: { expose: true } }, handler);
// Apply to raw endpoints only
middleware({ target: { isRaw: true } }, handler);
// Apply to streaming endpoints only
middleware({ target: { isStream: true } }, handler);
// Apply to endpoints with specific tags
middleware({ target: { tags: ["admin", "internal"] } }, handler);Middleware Request Object
The request object provides access to:
const myMiddleware = middleware(
{ target: { all: true } },
async (req, next) => {
// For typed and streaming APIs
const meta = req.requestMeta; // { method, path, pathParams }
// For raw endpoints
const rawReq = req.rawRequest;
const rawRes = req.rawResponse;
// For streaming endpoints
const stream = req.stream;
// Custom data to pass to handlers
req.data = { startTime: Date.now() };
const resp = await next(req);
// Modify response headers
resp.header.set("X-Response-Time", `${Date.now() - req.data.startTime}ms`);
return resp;
}
);Guidelines
- Services cannot be nested within other services
- Start with one service, split when there's a clear reason
- Use
~encore/clientsfor cross-service calls (never direct imports) - Each service can have its own database
- Service names should be lowercase, descriptive
- Don't create services just for code organization - use folders instead
npx skills add https://github.com/encoredev/skills --skill encore-serviceRun this in your project โ your agent picks the skill up automatically.
No common issues documented yet. If you hit a problem, the repository's GitHub Issues page is the best place to look.
Licensed under Apache-2.0โ you can use, modify, and redistribute it under that license's terms.
View the full license file on GitHub โ