Skip to main content
apps/api is the full-stack backend. One source tree hosts three entry points that share the same code — resources, schemas, the @/db service: The stack is Hono · oRPC · Drizzle ORM · PostgreSQL · better-auth · 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:
There’s one place for each thing, so you — and your agents — never guess where code lives. See 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 already applied — and declares its input, output and handler:
resources/users/endpoints/list.ts
.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 and 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:
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.tsimplement(contract).router({...}) wiring each endpoint into its slot, plus the exported AppClient type the web app consumes.
list.tsGET /users, create.tsPOST, [userId]/update.tsPUT /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:
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 for the full data-access model.

Migrations

Drizzle owns the schema lifecycle:
See 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:
From there, the isAuthorized and isAdmin 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).

Interactive API reference

In non-production, Hono serves a live Scalar 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 API reference
Browse your tables with Drizzle Studio too — run pnpm dashboard and open https://local.drizzle.studio.