Services & Isolation
A stackr monorepo is made of two kinds of service. The distinction is not cosmetic — it defines who is allowed to own which data.
The auth service (trust anchor)
There is exactly one auth service, and it is the only service permitted to own
identity data: the user, session, account, and verification tables. It runs
BetterAuth, issues httpOnly session cookies, and handles OAuth, email
verification, password reset, and two-factor auth.
Every other service treats it as the source of truth for "who is this request?".
The hard rule
Never declare user, session, account, or
verification tables outside the auth service. This isn't a style
preference — it's mechanically enforced by an
ast-grep rule
(no-auth-tables-outside-auth) and a Prisma-side check.
It also ships an optional admin dashboard (Next.js) on port :3333 for managing
users and roles — see Frontends.
Base services
A base service owns a slice of your product's domain logic and its own tables. It is fully self-contained:
- a Fastify backend on its own port (
:8080,:8081, …), - its own isolated PostgreSQL database,
- its own isolated Redis instance,
- optional Next.js web and Expo mobile frontends,
- an optional BullMQ event queue.
A base service authenticates callers by forwarding their cookie to the auth service
— it never grows its own copy of the user table. What a base service does track is
a per-service link to the user (a has<Service>Account flag and, typically, a domain
record keyed by user.id), provisioned on the user's first authenticated call.
Isolation in practice
Because each service owns its data outright, a failure stays local:
| Property | What it means in practice |
|---|---|
| Bounded blast radius | A bad migration or a runaway query in wallet can't corrupt core's data — they're different databases. |
| Independent schemas | Each service migrates on its own cadence. There is no monorepo-wide schema lockstep. |
| Decoupled deployment | Scale, redeploy, or rewrite one service without coordinating the others. |
| Clear ownership | Every table has exactly one owning service. Cross-service reads go over HTTP, never into a foreign database. |
What services never do
- Reach into another service's database. Cross-service data flows over REST with cookie forwarding, not SQL.
- Import another service's
domain/**code. There is no shared package; a service is a black box to its neighbors. - Trust a client-supplied user id or a bare header. Identity always comes from the auth service's verified response.
These invariants are written into the project's root AGENTS.md so your agent reads
them before touching cross-service code — see
The Context Harness.
Adding and shaping services
You declare your initial services in the wizard, and add more
later with stackr add service. The full list — names,
ports, ORM, middleware flavors — lives in stackr.config.json, the source of truth
the stackr CLI keeps in sync.