Build your first resource
The fastest way to understand a Beignet app is to generate one resource and follow the files it creates.
bunx -p @beignet/cli beignet make resource projects
The generator creates a compiling vertical slice. It is intentionally small: the
resource starts with a minimal name field so you can rename schemas, add
business fields, and replace or expand the generated persistence adapter.
Generated files
| File | Responsibility |
|---|---|
features/projects/contracts.ts | HTTP contract group, request schemas, response schemas, and exported contracts |
features/projects/use-cases/ | Validated application workflows for the resource |
features/projects/ports.ts | Feature-owned persistence boundary |
ports/index.ts | Central port registry used by app context |
infra/projects/in-memory-project-repository.ts | Default adapter behind the repository port |
infra/app-ports.ts | Runtime wiring for all ports |
features/projects/routes.ts | Feature-owned route group that maps contracts to use cases |
features/projects/tests/projects.test.ts | Starter test coverage for the generated use cases |
In apps created with the drizzle-turso integration, the command also creates
infra/db/schema/projects.ts, infra/projects/drizzle-project-repository.ts,
and registers the repository in infra/db/repositories.ts. The in-memory
repository remains useful as a test fake, while the server context uses the
Drizzle repository factory.
The command also registers the route group in the central route registry
(server/routes.ts in generated apps). OpenAPI routes that use
server.contracts or contractsFromRoutes(routes) pick up the new contracts
from the registered route list. If a test script is missing, the command adds
one.
Follow the request
The generated route follows the standard request path:
app/api/[[...path]]/route.ts
-> server.api
-> server/routes.ts route registration
-> features/projects/routes.ts
-> features/projects/contracts.ts request validation
-> createContext and hooks
-> features/projects/use-cases/*
-> ctx.ports.projects
-> infra/projects/*
-> features/projects/contracts.ts response validation
The catch-all route stays thin. The important decisions live in the contract, the feature route group, the use case, the port, and the infra adapter.
Edit in this order
- Rename the contract schemas in
features/projects/contracts.tsto match your domain. - Add request and response fields to the contract before changing handlers.
- Update the generated use cases to express the workflow.
- Expand
ProjectRepositoryonly with operations the use cases need. - In a Drizzle app, update
infra/db/schema/projects.tsandinfra/projects/drizzle-project-repository.tswith the durable fields and queries. In an in-memory starter, replace the adapter when the boundary is stable. - Run
bun run test,bun run typecheck,beignet lint, andbeignet doctor.
Preview writes
Use --dry-run when you want to see the files before changing the app:
bunx -p @beignet/cli beignet make resource projects --dry-run
Use JSON output when another tool needs the exact write plan:
bunx -p @beignet/cli beignet make resource projects --dry-run --json
Generated files are idempotent when unchanged. If an existing file has different content, the CLI stops instead of guessing how to merge it.