apps/api and apps/web lives in one place: the packages/ folder. Define a constant, a UI library, or a config once, depend on it as workspace:*, and every app stays in sync.
What’s in packages/
A scaffolded project ships these workspace packages:
There is no
shared package. The typed API client is not a package — it lives in the API workspace and reaches the web app through TypeScript declarations. See Typed client, no shared package below.Using a package
Workspace packages are referenced with theworkspace:* protocol. Add one to the dependencies of the app that needs it:
apps/api/package.json
pnpm install once to link the workspace, and the import resolves to your local source — no publish, no build step for the consumer.
Typed client, no shared package
End-to-end type safety does not flow through a shared package. It flows through the API workspace itself:- Endpoints declare their shape with
.input(zodSchema).output(zodSchema). pnpm --filter api build:typesemits.d.tsfiles for the API package.- The web app depends on
"api": "workspace:*"and imports the typed oRPC client:
apps/web — typed client
DbService in lockstep with the filesystem. After adding or removing an endpoint or schema file, run:
src/router.ts, src/contract.ts, and src/db.ts from the resources on disk — the types an agent (or you) reads are always real. See How Ship works for the resource model and Schemas for the schema layer.
Adding your own package
Create a folder underpackages/, give it a package.json with a workspace:*-friendly name, and add it to whichever app depends on it.
1
Scaffold the package
packages/billing/package.json
2
Depend on it
apps/api/package.json
3
Link the workspace
