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:
| Package | Name | Provides |
|---|---|---|
app-constants | app-constants | Cross-app enums and constants — USER_STATUSES, USER_AVATAR, token TTLs. Import these into Zod schemas instead of inlining string literals. |
db | @ship/db | The DbService class and MutationEvent types behind the generated @/db service. |
emails | @ship/emails | React Email templates, rendered server-side. Added by the mailer plugin. |
cloud-storage | @ship/cloud-storage | The S3-compatible cloudStorageService. Added by the cloud-storage plugin. |
eslint-config / prettier-config / tsconfig | same | Shared lint, format, and compiler config consumed by every workspace. |
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.
Read more about internal packages in the Turborepo documentation.
