A resource owns its data access
Rule
Every read or write to a table goes through that table’s resource. Directdb.<table> calls belong in the resource’s endpoints, methods and handlers — not in unrelated resources.
Why
Keeping access inside the resource keeps every update discoverable in one folder. When the table changes, the blast radius is one directory. Theusers resource reads and writes db.users; nothing outside resources/users/ reaches into that table.
Complex reads live in methods
Rule
Aggregations, multi-step queries and any non-trivial read live in the resource’smethods/ folder — not inlined across endpoints.
Why
Complex queries are the code most coupled to the schema, so they need maintenance when the data shape changes. Keeping them inresources/<name>/methods/ puts every such query in one predictable place, easy to find and update when <name>.schema.ts moves.
Put things where they’re used
Rule
Keep code as close as possible to where it runs. An endpoint’s schema, its ownership gate and its handler all live within the same resource folder.Why
Things that change together should sit together; things that don’t should stay apart. That’s what gives each resource clear boundaries —resources/users/ contains its schema (users.schema.ts), endpoints (endpoints/), gates (middlewares/) and side effects (handlers/), and nothing it doesn’t need.
Resources talk through events, not each other
Rule
One resource shouldn’t reach into another’s data service. Compose them in an endpoint, or — better — react to the other resource’s mutation events.Why
Calling one service from inside another creates circular dependencies and quietly couples two boundaries. Every write already publishes a typedMutationEvent ({ type, docs, prevDocs }); drop a handler in resources/<name>/handlers/ to react to another resource’s changes without importing it:
Import from the root with @/
Rule
Import from the@/ alias (the API src root) — never with ../ chains. So:
import service from '../../users/users.schema'.
Why
Absolute@/ imports survive moving files around and make it obvious where something actually lives. Note also that resource folders are plural (resources/users/, resources/notes/), and a resource’s schema is its <name>.schema.ts. Relative-only imports from the same folder are fine; reaching up with ../ is what we avoid.
See also
- API action — the endpoint builder these conventions shape.
- Validation — schemas and gates per resource.
- How Ship works — the resource-owns-everything model in full.
