Backend Development Guidelines
Use this skill for backend and API work across web/, worker/, and
packages/shared/.
When to Apply
- Creating or modifying tRPC routers and procedures
- Creating or modifying public API endpoints
- Creating or modifying queue processors, producers, or queue-backed workflows
- Building or refactoring backend services and repositories
- Working on backend auth, middleware, validation, or observability
- Updating Prisma or ClickHouse access patterns
- Adding a field, option, flag, or enum member to shared backend code
- Adding or fixing backend tests
How to Read This Skill
- Use this
SKILL.md when the task spans multiple backend areas or you need the
end-to-end reference map.
- Read only the specific reference file that matches the work when the scope is
narrower.
- If the task introduces a user-supplied URL, an outbound HTTP request, a new
integration, or touches secrets, RBAC, or redirect handling, also load the
shared
security-review skill before
designing or implementing the change.
Before Adding a New Concept
Before adding a field to a shared schema or payload, an option or flag on a
shared signature, an enum member, an env toggle, or a branch that exists for one
caller — or before concluding that no change is needed — read
references/new-concepts.md.
Quick Start Checklists
UI: New tRPC Feature
- Define the router in
features/[feature]/server/*Router.ts.
- Use the appropriate protected or public procedure.
- Authenticate with JWT-aware middleware.
- Check project/resource access and entitlements.
- Validate input with Zod v4.
- Put business logic in a service file.
- Use
traceException for error handling where relevant.
- Add unit or integration tests in
__tests__/.
- Access config via
env.mjs.
Existing Endpoint: Additive Field or Filter
Before coding, classify the change as a new endpoint, an additive field/filter
on an existing endpoint, or a semantic replacement/breaking change.
For an additive field/filter:
- Reuse the canonical predicate. For endpoints that already support field-group
selection, reuse their existing field-group/projection path.
- Preserve the endpoint's existing response contract: use the normal optional
partial-row schema and converter path for field-group endpoints; retain the
strict response schema and converter path for ordinary endpoints.
- Do not create API-version-specific field sets, casts, or "must be selected"
runtime assertions unless compatibility requires them.
- Extend examples and contracts; do not replace an existing filter example.
- Write one test per unique boundary, not one test per file touched.
SDKs: New Public API Endpoint
- Create the route in
pages/api/public/.
- Wrap it with
withMiddlewares and createAuthedProjectAPIRoute.
- Define types in
features/public-api/types/.
- Authenticate with basic auth.
- Validate query, body, and response with Zod schemas.
- Include API versioning in paths and schemas.
- Update Fern API definitions to match TypeScript types.
- Add end-to-end tests in
__tests__/async/.
Worker: New Queue Processor
- Create the processor in
worker/src/queues/.
- Define queue types in
packages/shared/src/server/queues.
- Place business logic in
features/ or worker/src/features/.
- Distinguish failed jobs from jobs that should succeed with a recorded error.
- Register the queue in
WorkerManager in app.ts.
- Add worker vitest coverage.
Core Principles
- tRPC procedures, public API routes, and queue processors delegate business
logic to services.
- Access configuration through
env.mjs; do not read process.env directly
outside env setup.
- Validate all external input with Zod v4.
- Use Prisma directly for simple CRUD and repositories for complex query access.
- Express new requirements in the vocabulary shared code already has; adding a
concept to a shared abstraction is the last resort, not the first.
- Use OpenTelemetry and DataDog for backend observability.
- Always filter project-scoped database queries by
projectId.
- Keep Fern API definitions in sync with public TypeScript API contracts.
- Keep backend tests independent and parallel-safe.
Live Examples
- tRPC router with project auth and Zod input:
web/src/features/events/server/eventsRouter.ts.
- Public API route with middleware and typed request/response schemas:
web/src/pages/api/public/datasets/index.ts.
- Worker queue processor with typed jobs, logging, and retry behavior:
worker/src/queues/evalQueue.ts.
- Tenant filters for Prisma and ClickHouse:
references/database-patterns.md.
Naming Conventions
- tRPC routers:
camelCaseRouter.ts, for example datasetRouter.ts.
- Services:
service.ts in the feature server directory.
- Queue processors:
camelCaseQueue.ts, for example evalQueue.ts.
- Public API routes: kebab-case filenames, for example
dataset-items.ts.
Anti-Patterns to Avoid
- Business logic in routes or procedures.
- Direct
process.env usage instead of env.mjs / env.ts.
- Missing error handling.
- Missing input validation.
- Missing
projectId filters on tenant-scoped queries.
console.log instead of logger / traceException.
Reference Map
| Topic |
Read this when |
File |
| Architecture and package boundaries |
You need the web/worker/shared split, request flow, or queue lifecycle |
references/architecture-overview.md |
| Routing and controllers |
You are writing tRPC procedures, public API routes, or queue entrypoints |
references/routing-and-controllers.md |
| Middleware and auth |
You are changing request auth, permissions, or middleware composition |
references/middleware-guide.md |
| Services and repositories |
You are placing business logic, repository code, or DI patterns |
references/services-and-repositories.md |
| Database access |
You are touching Prisma, ClickHouse, tenant filters, or query patterns |
references/database-patterns.md |
| New concepts in shared code |
You are adding a field, option, flag, or enum member to a shared abstraction |
references/new-concepts.md |
| Configuration |
You are adding env vars, startup config, or runtime toggles |
references/configuration.md |
| Testing |
You are adding or updating backend tests |
references/testing-guide.md |
1---2name: backend-dev-guidelines3description: Build or review Langfuse backend code. Use for tRPC routers, public REST APIs, BullMQ processors, services, middleware, Prisma or ClickHouse access, OpenTelemetry, Zod, environment configuration, or backend tests.4---5
6# Backend Development Guidelines
7
8Use this skill for backend and API work across `web/`, `worker/`, and
9`packages/shared/`.
10
11## When to Apply
12
13- Creating or modifying tRPC routers and procedures
14- Creating or modifying public API endpoints
15- Creating or modifying queue processors, producers, or queue-backed workflows
16- Building or refactoring backend services and repositories
17- Working on backend auth, middleware, validation, or observability
18- Updating Prisma or ClickHouse access patterns
19- Adding a field, option, flag, or enum member to shared backend code
20- Adding or fixing backend tests
21
22## How to Read This Skill
23
24- Use this `SKILL.md` when the task spans multiple backend areas or you need the
25 end-to-end reference map.
26- Read only the specific reference file that matches the work when the scope is
27 narrower.
28- If the task introduces a user-supplied URL, an outbound HTTP request, a new
29 integration, or touches secrets, RBAC, or redirect handling, also load the
30 shared [`security-review`](../security-review/SKILL.md) skill before
31 designing or implementing the change.
32
33## Before Adding a New Concept
34
35Before adding a field to a shared schema or payload, an option or flag on a
36shared signature, an enum member, an env toggle, or a branch that exists for one
37caller — or before concluding that no change is needed — read
38[`references/new-concepts.md`](references/new-concepts.md).
39
40## Quick Start Checklists
41
42### UI: New tRPC Feature
43
44- Define the router in `features/[feature]/server/*Router.ts`.
45- Use the appropriate protected or public procedure.
46- Authenticate with JWT-aware middleware.
47- Check project/resource access and entitlements.
48- Validate input with Zod v4.
49- Put business logic in a service file.
50- Use `traceException` for error handling where relevant.
51- Add unit or integration tests in `__tests__/`.
52- Access config via `env.mjs`.
53
54### Existing Endpoint: Additive Field or Filter
55
56Before coding, classify the change as a new endpoint, an additive field/filter
57on an existing endpoint, or a semantic replacement/breaking change.
58
59For an additive field/filter:
60
61- Reuse the canonical predicate. For endpoints that already support field-group
62 selection, reuse their existing field-group/projection path.
63- Preserve the endpoint's existing response contract: use the normal optional
64 partial-row schema and converter path for field-group endpoints; retain the
65 strict response schema and converter path for ordinary endpoints.
66- Do not create API-version-specific field sets, casts, or "must be selected"
67 runtime assertions unless compatibility requires them.
68- Extend examples and contracts; do not replace an existing filter example.
69- Write one test per unique boundary, not one test per file touched.
70
71### SDKs: New Public API Endpoint
72
73- Create the route in `pages/api/public/`.
74- Wrap it with `withMiddlewares` and `createAuthedProjectAPIRoute`.
75- Define types in `features/public-api/types/`.
76- Authenticate with basic auth.
77- Validate query, body, and response with Zod schemas.
78- Include API versioning in paths and schemas.
79- Update Fern API definitions to match TypeScript types.
80- Add end-to-end tests in `__tests__/async/`.
81
82### Worker: New Queue Processor
83
84- Create the processor in `worker/src/queues/`.
85- Define queue types in `packages/shared/src/server/queues`.
86- Place business logic in `features/` or `worker/src/features/`.
87- Distinguish failed jobs from jobs that should succeed with a recorded error.
88- Register the queue in `WorkerManager` in `app.ts`.
89- Add worker vitest coverage.
90
91## Core Principles
92
93- tRPC procedures, public API routes, and queue processors delegate business
94 logic to services.
95- Access configuration through `env.mjs`; do not read `process.env` directly
96 outside env setup.
97- Validate all external input with Zod v4.
98- Use Prisma directly for simple CRUD and repositories for complex query access.
99- Express new requirements in the vocabulary shared code already has; adding a
100 concept to a shared abstraction is the last resort, not the first.
101- Use OpenTelemetry and DataDog for backend observability.
102- Always filter project-scoped database queries by `projectId`.
103- Keep Fern API definitions in sync with public TypeScript API contracts.
104- Keep backend tests independent and parallel-safe.
105
106## Live Examples
107
108- tRPC router with project auth and Zod input:
109 `web/src/features/events/server/eventsRouter.ts`.
110- Public API route with middleware and typed request/response schemas:
111 `web/src/pages/api/public/datasets/index.ts`.
112- Worker queue processor with typed jobs, logging, and retry behavior:
113 `worker/src/queues/evalQueue.ts`.
114- Tenant filters for Prisma and ClickHouse:
115 `references/database-patterns.md`.
116
117## Naming Conventions
118
119- tRPC routers: `camelCaseRouter.ts`, for example `datasetRouter.ts`.
120- Services: `service.ts` in the feature server directory.
121- Queue processors: `camelCaseQueue.ts`, for example `evalQueue.ts`.
122- Public API routes: kebab-case filenames, for example `dataset-items.ts`.
123
124## Anti-Patterns to Avoid
125
126- Business logic in routes or procedures.
127- Direct `process.env` usage instead of `env.mjs` / `env.ts`.
128- Missing error handling.
129- Missing input validation.
130- Missing `projectId` filters on tenant-scoped queries.
131- `console.log` instead of `logger` / `traceException`.
132
133## Reference Map
134
135| Topic | Read this when | File |
136| ----------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
137| Architecture and package boundaries | You need the web/worker/shared split, request flow, or queue lifecycle | [references/architecture-overview.md](references/architecture-overview.md) |
138| Routing and controllers | You are writing tRPC procedures, public API routes, or queue entrypoints | [references/routing-and-controllers.md](references/routing-and-controllers.md) |
139| Middleware and auth | You are changing request auth, permissions, or middleware composition | [references/middleware-guide.md](references/middleware-guide.md) |
140| Services and repositories | You are placing business logic, repository code, or DI patterns | [references/services-and-repositories.md](references/services-and-repositories.md) |
141| Database access | You are touching Prisma, ClickHouse, tenant filters, or query patterns | [references/database-patterns.md](references/database-patterns.md) |
142| New concepts in shared code | You are adding a field, option, flag, or enum member to a shared abstraction | [references/new-concepts.md](references/new-concepts.md) |
143| Configuration | You are adding env vars, startup config, or runtime toggles | [references/configuration.md](references/configuration.md) |
144| Testing | You are adding or updating backend tests | [references/testing-guide.md](references/testing-guide.md) |