> ## 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.

# Overview

> The Ship API: Hono + oRPC over Drizzle and PostgreSQL, with a contract-first router generated from the filesystem.

`apps/api` is the full-stack backend. One source tree hosts three entry points that share the same code — resources, schemas, the `@/db` service:

| Service       | Entry                          | Job                                                                                                 |
| ------------- | ------------------------------ | --------------------------------------------------------------------------------------------------- |
| **API**       | `src/app.ts` → `src/server.ts` | The HTTP server: a [Hono](https://hono.dev/) app serving the [oRPC](https://orpc.unnoq.com/) router |
| **Scheduler** | `src/scheduler.ts`             | A standalone process that runs crons and background jobs — see [Scheduler](/docs/scheduler)              |
| **Migrator**  | `scripts/migrate.ts`           | Applies pending Drizzle migrations against PostgreSQL — see [Migrator](/docs/migrator)                   |

The stack is [Hono](https://hono.dev/) · [oRPC](https://orpc.unnoq.com/) · [Drizzle ORM](https://orm.drizzle.team/) · [PostgreSQL](https://www.postgresql.org/) · [better-auth](https://better-auth.com/) · [Socket.IO](https://socket.io/). No controllers, no route registry — the filesystem is the wiring.

## A resource owns everything

Every business entity is a folder under `apps/api/src/resources/<name>/`. The resource owns its table, its schemas, its endpoints, its side effects and its gates:

```
resources/users/
  users.schema.ts          # Drizzle table + Zod schemas
  endpoints/               # oRPC endpoints, mounted by file path
    list.ts                # GET  /users
    current.get.ts         # GET  /users/current
    [userId]/update.ts     # PUT  /users/{userId}
  methods/                 # reusable business logic
  handlers/                # mutation-event handlers (side effects)
  middlewares/             # per-resource gates (e.g. can-edit-*.ts)
```

There's one place for each thing, so you — and your agents — never guess where code lives. See [How Ship works](/docs/how-ship-works) for the full model.

## Endpoints are oRPC

Every endpoint builds on the shared `@/endpoint` builder — the oRPC builder with all [global middlewares](/docs/api-reference/middlewares) already applied — and declares its input, output and handler:

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

export default endpoint
  .use(isAdmin)
  .input(paginationSchema)
  .output(listResultSchema(publicSchema))
  .handler(async ({ input }) => {
    return db.users.findPage({ where: { deletedAt: null }, ...input });
  });
```

`.input()` / `.output()` are Zod schemas — the single source of truth for the runtime *and* the types the client sees. Gates compose with `.use(...)`. The handler returns a value; oRPC serialises it. See [Routing](/docs/api-reference/routing/overview) and [Middlewares](/docs/api-reference/routing/middlewares).

## Contract-first, generated from the filesystem

oRPC separates a thin **contract** (route methods + paths) from the **router** (the implementations). Ship generates both from your endpoint files so you never hand-edit a route table:

```bash theme={null}
pnpm --filter api codegen
```

`scripts/codegen-router.ts` walks `resources/**/endpoints/` and writes:

* **`src/contract.ts`** — the `oc.router(...)` contract, one `oc.route({ method, path })` per endpoint file.
* **`src/router.ts`** — `implement(contract).router({...})` wiring each endpoint into its slot, plus the exported `AppClient` type the web app consumes.

`list.ts` → `GET /users`, `create.ts` → `POST`, `[userId]/update.ts` → `PUT /users/{userId}`. The route table *is* the directory tree.

## Data access is a typed service

`scripts/codegen-db.ts` generates a `DbService` per table, exported from `@/db` — a thin, typed wrapper over Drizzle with a uniform filter API:

```ts theme={null}
await db.users.findPage({
  where: { deletedAt: null, email: { ilike: '%@acme.com' } },
  orderBy: { createdAt: 'desc' },
  page: 1,
  perPage: 20,
});
```

`find` / `findFirst` / `findPage` / `count` / `insertOne` / `insertMany` / `updateOne` / `updateMany` / `deleteOne` / `deleteMany`, plus `db.transaction(...)` and relation loading via `with` / `columns`. Every table extends `baseColumns` — a `uuid` id, `createdAt`, `updatedAt` and a `deletedAt` soft-delete column. Mutations emit a typed `MutationEvent`; drop a handler in `<resource>/handlers/` to react. The `DbService` lives in the `@ship/db` package; see [How Ship works](/docs/how-ship-works) for the full data-access model.

## Migrations

Drizzle owns the schema lifecycle:

```bash theme={null}
pnpm --filter api generate    # drizzle-kit emits SQL into apps/api/drizzle/
pnpm --filter api migrate     # apply pending migrations (scripts/migrate.ts)
pnpm --filter api db:push     # push the schema directly (dev only)
```

See [Migrator](/docs/migrator) for the full flow.

## Auth and context

better-auth resolves the session on every request. `server.ts` builds an `ORPCContext` and calls `serverConfig.resolveUser`, which reads the better-auth session from the request headers and attaches the row to `context.user` when signed in:

```ts theme={null}
const session = await auth.api.getSession({ headers: ctx.rawRequest.headers });
if (session?.user) {
  ctx.user = await db.users.findFirst({ where: { id: session.user.id } });
}
```

From there, the [`isAuthorized`](/docs/api-reference/routing/middlewares) and [`isAdmin`](/docs/api-reference/routing/middlewares) gates read `context.user`. The full auth surface — sign-in/up, verification, reset, Google OAuth, plus the web pages — is delivered by the Auth plugin (see [Plugins](/docs/plugins/overview)).

## Interactive API reference

In non-production, Hono serves a live [Scalar](https://scalar.com/) UI in-process. `server.ts` generates an OpenAPI 3.1 spec from the router and renders the docs with an interactive "try it out":

* **Scalar UI** — [http://localhost:3001/docs](http://localhost:3001/docs)
* **Raw spec** — `http://localhost:3001/spec.json`

<img src="https://mintcdn.com/ship/UByoaHTxaOwKkFqs/images/dashboards/scalar-docs.png?fit=max&auto=format&n=UByoaHTxaOwKkFqs&q=85&s=d42569043e97fee0146272a3d7fd9d5c" alt="Scalar API reference" width="2880" height="2200" data-path="images/dashboards/scalar-docs.png" />

<Tip>Browse your tables with [Drizzle Studio](https://orm.drizzle.team/drizzle-studio/overview) too — run `pnpm dashboard` and open [https://local.drizzle.studio](https://local.drizzle.studio).</Tip>
