> ## Documentation Index
> Fetch the complete documentation index at: https://ship.paralect.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# API conventions

> A few conventions keep the API discoverable and refactor-safe. They're not enforced by the compiler — follow them and you can keep shipping fast years in.

These are conventions, not compiler rules. Ship doesn't force them, but every resource in the template and every plugin follows them — and that consistency is what lets you (and your agents) add code without first reverse-engineering the last person's choices. **Standardised patterns. Zero guesswork.**

## A resource owns its data access

### Rule

Every read or write to a table goes through that table's resource. Direct `db.<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. The `users` resource reads and writes `db.users`; nothing outside `resources/users/` reaches into that table.

```ts theme={null}
// resources/users/endpoints/list.ts
return db.users.findPage({ where: { deletedAt: null }, orderBy: sort, page, perPage });
```

## Complex reads live in methods

### Rule

Aggregations, multi-step queries and any non-trivial read live in the resource's `methods/` 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 in `resources/<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 typed `MutationEvent` (`{ type, docs, prevDocs }`); drop a handler in `resources/<name>/handlers/` to react to another resource's changes without importing it:

```ts theme={null}
// resources/users/handlers/on-user-deleted.ts — react, don't reach in
export default async function onUserDeleted({ docs }: MutationEvent<User>) {
  // clean up things that belong to this user
}
```

## Import from the root with `@/`

### Rule

Import from the `@/` alias (the API `src` root) — never with `../` chains. So:

```ts theme={null}
import db from '@/db';
import endpoint from '@/endpoint';
import isAdmin from '@/middlewares/is-admin';
import { publicSchema } from '@/resources/users/users.schema';
```

not `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](/docs/api-reference/api-action) — the endpoint builder these conventions shape.
* [Validation](/docs/api-reference/api-action-validator) — schemas and gates per resource.
* [How Ship works](/docs/how-ship-works) — the resource-owns-everything model in full.
