Multi-tenancy
Nwire partitions every store, every projection, and every actor by tenant when the app declares a tenant model. One process can serve N tenants with no bleed.
Declare on the app
import { createApp } from "@nwire/app"
import { forgePlugins } from "@nwire/forge"
const app = createApp({
appName: "submissions",
handlers: [/* … */],
plugins: [...forgePlugins({ /* actors, projections, … */ })],
})There's no tenantModel setting — the envelope carries the tenant. Configure the inbound transport with the header name; everything downstream reads envelope.tenant. See the Multi-tenancy guide for the full picture.
What happens
- Every actor store op is keyed by
(tenant, aggregateId). - Every projection store op is keyed by
(tenant, projectionKey). - Every action's
envelope.tenantis populated from the request header. - Handlers and reactions can read
ctx.envelope.tenantand use it as-needed.
HTTP wire
httpKoa() reads the x-tenant header by default and stamps envelope.tenant. Handlers can read the tenant from ctx.envelope.tenant. For tenant-required dispatches, validate inside the action handler and throw a typed error.
curl -X POST http://localhost:3000/submissions \
-H 'x-tenant: acme' \
-H 'Content-Type: application/json' \
-d '{"studentId":"avi","answer":"א"}'Queue + bus
Tenant flows through automatically. The envelope is serialized on dispatch and deserialized on receive — no manual passing.
Excluding a route
Some routes (sign-in, health, signup) run before a tenant exists. Mark the action tenant: "none" to opt out of the requirement.
See also
- Hash partitioning vs row-level — when to put tenants in separate databases
- @nwire/forge tenant API —
actorStore.list({ tenant }),projectionStore.byKey({ tenant, key })