Modules
Module Organization
The Bun Core Platform uses a layered module architecture with three main layers:- Apps — Entry points and orchestration
- Packages — Shared code (domain, application, infrastructure)
- Config — Centralized configuration
Apps Layer
apps/monolith/
The main Hono application. Wires together:
- Server setup (Hono)
- Middleware registration (rate limiting, idempotency, i18n, logging)
- Route mounting (
/api/v1/...) - DI container setup
Packages Layer
packages/auth-ports/ and packages/auth-adapter/
The one live hexagon, and the arrangement to copy for a new seam.
- A port imports no library.
auth.port.tsnames no Better Auth type. - The adapter is the only file that does, and it translates library errors into
@bun-core/shared-kernelones at the boundary — see Ports & Adapters. - There is no entity, repository or use case between them. Add a layer when something calls it.
packages/infrastructure/
Adapters and infrastructure services, each behind a port where one exists.
packages/shared-kernel/
ApplicationError and its subclasses (UnauthorizedError, ConflictError, …), the state
machine, and shared types. Depends only on zod (for ValidationError) and ulid — no
framework or infrastructure. This is what an adapter is required to throw, so
error.middleware.ts can map it to a status code — AuthAdapter doesn’t yet. Methods that
propagate a failure throw a bare Error, the open task 7.6 exception; getSession() is the one
exception to the exception — it catches the failure and returns null instead of throwing. See
Ports & Adapters.
packages/users-domain/
A validation package, not a domain. It holds the Zod DTO schemas for the users HTTP surface
and nothing else — no entity, no repository, no use case. Better Auth owns the user model.
Config Layer
apps/monolith/src/config/env.ts
Every environment variable, validated with Zod. Read it through loadEnv(), never by casting
process.env — a raw cast leaves defaults unapplied and string flags like 'false' truthy.
Module Rules
Adding a New Seam
packages/<name>-ports/ports/<name>.port.ts— the interface, with JSDoc.packages/<name>-adapter/<library>.adapter.ts— the implementation.- Add both to
apps/monolith/package.jsonand runbun install. - Wire one field in
DIContainer. - Mount routes under
apps/monolith/src/services/<name>/.
backend/bun/AGENTS.md § “How to Add a New Domain” is the step-by-step version.
See Also
- Architecture — System design
- File Structure — Project layout