App architecture

A Beignet app keeps production code in a small set of predictable places, and each folder owns one kind of decision. This page is the map: what lives where, where each production concern goes, and the dependency direction beignet lint enforces. For the first-hour loop, use Quickstart; for the guided tour of one feature, use Build your first feature.

What goes where

PathResponsibility
features/<feature>/contracts.tsHTTP surface: method, path, params, request body, headers, responses, metadata, and catalog errors
features/<feature>/schemas.tsShared DTO and validation schemas that contracts, use cases, ports, client modules, and tests may import
features/<feature>/routes.tsFeature route group that maps contracts to use cases
features/<feature>/use-cases/Application workflows with input and output validation
features/<feature>/domain/Feature-owned entities, value objects, and domain events
features/<feature>/components/Feature-owned UI and client workflows
features/<feature>/policy.tsFeature-owned authorization rules
features/<feature>/ports.tsFeature-specific dependency interfaces such as repositories
features/<feature>/notifications/, uploads/, jobs/, listeners/, schedules/, tasks/, seeds/Feature-owned workflow artifacts, added by generators when needed
features/<feature>/tests/Feature behavior tests, with shared factories in tests/factories/
features/shared/errors.tsApplication error catalog and route-owned error schemas
features/shared/domain/Shared-kernel domain concepts used across features
server/routes.tsCentral route registry and OpenAPI contract list
server/context.tsShared context blueprint reused by the runtime server and route tests
server/index.tsRuntime wiring: context, hooks, providers, and error mapping
server/providers.tsBeignet lifecycle providers installed at server startup
server/tasks.ts, server/outbox.ts, server/schedules.tsApp-owned registries and CLI contexts for tasks, outbox draining, and schedules
app/api/Thin Next.js route files that expose server.api
app-context.tsShared request context type used by handlers, hooks, and use cases
ports/App-wide dependency interfaces shared across features
infra/Concrete adapters and default port wiring, including infra/app-ports.ts
lib/Small app helpers: env.ts, auth.ts, and the use-case.ts builder
client/Typed Beignet client and frontend adapter factories

The CLI starter scaffolds the subset of this structure it ships: contracts, schemas, routes, use cases, components, ports, infra, server composition, and the client. Workflow-tier artifacts such as jobs, listeners, notifications, schedules, uploads, the outbox registry, and operational tasks appear when beignet make generators add them, along with their lib/ builders and server registries. beignet doctor treats their absence as fine and their misplacement as drift.

Production concern map

When you know what you need to build, this table says where it goes:

ConcernPut it hereRead next
Endpoint shapefeatures/<feature>/contracts.tsContracts
Feature route wiringfeatures/<feature>/routes.tsServer
Request routingserver/routes.ts, server/index.ts, and app/api/Routes and server
Request lifecycle behaviorserver hooksRequest lifecycle, Hooks
Business workflowfeatures/<feature>/use-cases/Use cases
Business authorizationfeatures/<feature>/policy.ts or app-owned policy helpersAuthorization
Persistence and transactionsfeature repository ports plus ctx.ports.uow.transaction(...)Database and transactions
Audit/activity loggingctx.ports.audit plus request actor, tenant, and requestIdAudit and activity logging
Cached reads and invalidationctx.ports.cache from infra/ or a cache providerCache
Object storagectx.ports.storage from infra/ or a storage providerStorage
Uploadsfeatures/<feature>/uploads/, StoragePort, and app-owned attachment recordsUploads
Domain eventsfeatures/<feature>/domain/events/, feature listeners, Unit of Work event recorderEvents
Background workctx.ports.jobs and job definitionsJobs
Scheduled workfeatures/<feature>/schedules/ and a cron/provider triggerSchedules
Mailctx.ports.mailer and mail provider adaptersMail
Notificationsfeatures/<feature>/notifications/ and ctx.ports.notificationsNotifications
Structured loggingctx.ports.logger and request logging hooksLogging
Rate limitingcontract metadata plus rate limit hooksRate limiting
Provider startup and teardownserver/providers.tsProviders
Env vars and deployment configlib/env.tsConfig, Deployment
App errorsfeatures/shared/errors.ts and contract .errors(...)Errors
OpenAPI routeapp/api/openapi/route.tsOpenAPI
Dev-only request inspectionapp/api/devtools/[[...path]]/route.tsDevtools
UI data fetchingclient/, features/<feature>/components/, React QueryReact, React Query

Dependency direction

The rule is simple: transport, application behavior, and infrastructure do not own each other. A feature owns its contracts, route group, use cases, policy, domain model, UI, and feature-specific ports. App-wide ports describe dependencies shared across features. Infra implements those ports. Domain concepts that are genuinely shared across features live in features/shared/domain/.

beignet lint enforces the most important directions. Domain and use cases cannot import infra, UI, route, client, provider, or framework code. Feature domain cannot import another feature's domain unless it comes from features/shared/domain. Route files cannot import infra or UI, and infra adapters cannot import UI, routes, server modules, or clients. Contract files and everything reachable from client/ or "use client" modules are also checked as client-safe import graphs that must not reach server-only code; see the CLI reference for the full lint rules.

Two placement notes that follow from the rule:

Feature-owned UI

Product UI lives with the feature it serves, in features/<feature>/components/. Shared client wiring — the typed Beignet client, React Query helper, React Hook Form helper, upload client, and QueryClient provider — lives in client/. Feature components import those helpers plus their feature's contracts, and call endpoints through React Query and form adapters.

components/ may contain React Server Components and Client Components. Server Components can call server-only modules when they are not reachable from a client root. Client Components and anything they import cannot reach use cases, route groups, infra adapters, server modules, provider packages, or app-context.ts — keep server-only workflows behind route groups and explicit server entrypoints. See React for the component patterns.

Server composition

Features keep route wiring local in features/<feature>/routes.ts, and the server composes them at the boundary: server/routes.ts owns the central route list, server/context.ts declares the context blueprint once for the runtime and route tests, server/index.ts assembles ports, providers, hooks, and routes, and a catch-all app/api/[[...path]]/route.ts exposes server.api. That convention gives the CLI one source of truth for route inspection, generation, OpenAPI wiring, and drift checks. The code for each piece is on Routes and server.

Custom paths

Use beignet.config.ts when your app keeps the same architecture under different paths:

import { defineConfig } from "@beignet/cli/config";

export default defineConfig({
  paths: {
    contracts: "src/features",
    features: "src/features",
    routes: "src/app/api",
    server: "src/core/server/index.ts",
  },
});

Config changes where the CLI looks and writes. It does not replace the architecture: feature contracts still define the HTTP boundary, the server still registers route groups, and application code still belongs behind use cases and ports.