Mental model
Beignet uses a small, fixed vocabulary across the docs, the CLI, and generated apps. This page defines each term once. Read it after Quickstart, before the deeper pages lean on these words.
The HTTP boundary
- Contract — describes one HTTP endpoint: method, path, parameters, request body, response statuses, and metadata. Contracts feed server validation, typed clients, React Query and form helpers, and OpenAPI generation. See Contracts.
- Route group — a feature-owned list that maps contracts to use cases,
defined in
features/<feature>/routes.tsand composed centrally inserver/routes.ts. See Routes and server. - Hook — an ordered lifecycle function for infrastructure behavior at the HTTP boundary: auth, CORS, rate limits, logging, response shaping, and error mapping. Hooks can short-circuit before the handler, enrich context, observe responses, or map errors. See Hooks.
- Context — the per-request value (
ctx) the server builds from your context blueprint: the actor, the session, the request id, and the app's ports. Use cases and hooks read everything through it. - Error catalog — the app-owned set of named business errors in
features/shared/errors.ts. Contracts declare them with.errors(...), use cases throw them, and clients receive them typed. Framework-owned responses such as validation failures stay distinguishable from catalog errors. See Errors and Request lifecycle for how response ownership works.
Application code
- Use case — a validated application workflow (a command or a query) with typed input and output. The same use case can run from HTTP routes, jobs, schedules, scripts, and tests. See Use cases.
- Policy — feature-owned business authorization rules. Hooks decide who is signed in; policies decide what they may do. See Authorization.
- Actor, tenant, and gate — the actor is who is acting, the tenant is the organization scope they act in, and the gate checks policies against both. All three live on the request context.
- Domain — optional helpers for entities, value objects, and domain events when a feature wants more structure around core business concepts. See Domain modeling.
Dependencies
- Port — an app-facing dependency interface, used through
ctx.ports. Feature repositories live infeatures/<feature>/ports.ts; app-wide ports live inports/. See Ports and adapters. - Adapter — a concrete implementation of a port, kept in
infra/and wired ininfra/app-ports.ts. - Provider — a package that installs or replaces ports at server startup: Drizzle, Redis, Pino, Better Auth, Inngest, mail services, and more. Tests pass mock ports directly instead. See Providers.
- Unit of Work (UoW) — the transaction boundary at
ctx.ports.uow.transaction(...). It commits repository writes together and records events and outbox messages that run after commit. See Database and transactions.
Workflow primitives
Beyond the request path, Beignet names background concepts by the question they answer:
- Event — "this fact happened"; listeners react to it.
- Job — "do this work later or outside the request."
- Schedule — "start this workflow at this time."
- Notification — "tell a person or team about this."
- Task — "run this operational entrypoint", such as a backfill.
- Idempotency key — "this logical command may arrive again."
- Outbox record — "this side effect must commit with the database write."
See Workflow primitives for the decision table and the common combinations.
API grammar
Beignet APIs follow one naming rule, so new packages feel predictable:
defineXdeclares something you register: contracts, routes, ports, errors, events, jobs, schedules, policies.createXbuilds a runtime object you call: servers, clients, providers, and the per-capability factories such ascreateJobs<AppContext>()that return app-bounddefineJobbuilders.
Builders are immutable: each chained method refines the definition and returns the next builder. Contract-aware adapters then accept the contract you already exported:
export const createPost = posts
.post("/")
.body(CreatePostInputSchema)
.responses({ 201: PostSchema });
const endpoint = client.endpoint(createPost);
const mutation = rq(createPost).mutationOptions();
const form = rhf(createPost).useForm();Define the shape once, then bind it to the runtime surface you need. That is the transfer rule behind every Beignet integration.