Skip to main content
Ship has no route registry. You drop an endpoint file into a resource, and codegen mounts it. The route table is the directory tree — standardised patterns, zero guesswork.

Endpoints mount by file path

Each file under apps/api/src/resources/<name>/endpoints/ is one endpoint. Its method and HTTP path come from the file name and folder structure: The conventions, from scripts/codegen-router.ts:
  • CRUD nameslistGET (collection), createPOST (collection), getGET, updatePUT, deleteDELETE.
  • Method suffixname.post.tsPOST /name, status.get.tsGET /status. Use this for non-CRUD actions.
  • [param] folders[userId]/ becomes the {userId} dynamic segment.
  • Resource name = path prefixresources/users//users/*.
An endpoint that needs a non-default route declares it inline with .route(...), which overrides the convention:

Contract-first, generated

oRPC splits a thin contract (routes + schemas) from the router (implementations). Ship generates both from the filesystem, so the wiring is always real:
codegen-router.ts discovers every resource and endpoint, then writes two files. Both are generated — never edit them by hand. src/contract.ts carries one route per endpoint:
contract.ts
src/router.ts implements that contract by slotting each endpoint into place, and exports the AppClient type the web app imports:
router.ts
Codegen also imports each resource’s handlers/* (so mutation-event side effects register) and emits a parallel spec-only router that feeds the Scalar docs. Run it after adding or removing an endpoint file — pnpm --filter api dev runs it in --watch mode for you.

Request flow

server.ts builds an ORPCContext for each request — cookies, headers, and the better-auth session resolved into context.user. The oRPC handler then runs the endpoint’s global middlewares, its gates, and .input() validation before the handler.

Session context

Auth is contextual, not a route flag. serverConfig.resolveUser reads the better-auth session from the request headers and attaches the user row to context.user when present:
server-config.ts
Endpoints stay unauthenticated until you stack a gate. Add .use(isAuthorized) to require a signed-in context.user; add .use(isAdmin) to require an admin. See Middlewares.

See also