The TypeScript fullstack preset’s superpower is not “full stack”—it is one type graph from the database outward. Arche wires the folders so you do not accidentally put routers in the wrong package.
The pipeline
packages/store Prisma schema + client
↓
apps/server modules/*/*.trpc.ts (procedures)
↓
packages/trpc AppRouter types + createCaller (client-only)
↓
apps/web trpc client + trpcCaller (RSC)Common mistake: routers in packages/trpc
packages/trpc re-exports types. Procedures live in:
apps/server/src/modules/<feature>/*.trpc.tscomposed in apps/server/src/modules/trpc/app.router.ts.
If you define routers in packages/trpc, you pull server dependencies into the wrong layer and fight the bundle. The tRPC package doc states this loudly for a reason.
Server-side data fetching (no HTTP loopback)
const api = await trpcCaller()
const posts = await api.posts.list()trpcCaller uses createCaller in-process—good for RSC and server actions.
Client-side fetching
trpc.posts.list.useQuery()HTTP to NEXT_PUBLIC_API_URL with the shared AppRouter type.
Validation lives with procedures
Zod (or shared validators) at procedure boundaries. Services hold business logic; repositories touch Prisma; policies answer authorization. The Architecture page describes module layout.
When types lie
Types are only honest if you:
- Run
db:generateafter schema changes - Rebuild server before trusting web types in CI
- Do not cast around
AppRouterwithany
Arche gives you the graph—you still have to run the tasks.
Beyond TypeScript
Rust fullstack uses explicit API boundaries instead of tRPC. Convex uses its client. The “glue” idea transfers: one contract surface, thin apps.
Start here: Getting started and scaffold:
bun run dev:cli -- my-app --yes --preset=typescript-fullstack