Modules

Module Organization

The Bun Core Platform uses a layered module architecture with three main layers:
  1. Apps — Entry points and orchestration
  2. Packages — Shared code (domain, application, infrastructure)
  3. 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

This page used to list packages/domain/, packages/application/ and a modules/gateway/ tree. None of them exist. The domain/application split was never built here, and the gateway module is ClawUI, which is KIV (ADR-0053 removed the parallel attempt in users-domain). What follows is the real layout.

packages/auth-ports/ and packages/auth-adapter/

The one live hexagon, and the arrangement to copy for a new seam.
Rules:
  • A port imports no library. auth.port.ts names no Better Auth type.
  • The adapter is the only file that does, and it translates library errors into @bun-core/shared-kernel ones 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

  1. packages/<name>-ports/ports/<name>.port.ts — the interface, with JSDoc.
  2. packages/<name>-adapter/<library>.adapter.ts — the implementation.
  3. Add both to apps/monolith/package.json and run bun install.
  4. Wire one field in DIContainer.
  5. 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