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

FileResponsibility
features/projects/contracts.tsHTTP contract group, request schemas, response schemas, and exported contracts
features/projects/use-cases/Validated application workflows for the resource
features/projects/ports.tsFeature-owned persistence boundary
ports/index.tsCentral port registry used by app context
infra/projects/in-memory-project-repository.tsDefault adapter behind the repository port
infra/app-ports.tsRuntime wiring for all ports
features/projects/routes.tsFeature-owned route group that maps contracts to use cases
features/projects/tests/projects.test.tsStarter 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

  1. Rename the contract schemas in features/projects/contracts.ts to match your domain.
  2. Add request and response fields to the contract before changing handlers.
  3. Update the generated use cases to express the workflow.
  4. Expand ProjectRepository only with operations the use cases need.
  5. In a Drizzle app, update infra/db/schema/projects.ts and infra/projects/drizzle-project-repository.ts with the durable fields and queries. In an in-memory starter, replace the adapter when the boundary is stable.
  6. Run bun run test, bun run typecheck, beignet lint, and beignet 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.