Architecture Overview
Monorepo layout, packages, API middleware stack, and how it all fits together.
This page describes the high-level architecture of the Toast codebase — how the monorepo is organized, what each package does, and how the API server processes requests.
Monorepo Layout
Toast is a pnpm workspace monorepo with four package groups:
apps/
api/ # Hono API server (Node.js)
admin/ # React 19 admin panel (Vite + nginx)
docs/ # Documentation site (Next.js + Fumadocs)
shared/
contracts/ # Shared TypeScript types, Zod schemas, permissions
packages/
db/ # Drizzle ORM schema, migrations, seed
ui/ # Shared React components (Base UI)
config/ # Shared ESLint and TypeScript configuration
drivers/
email-* # Email provider drivers (Mailgun, Postmark, etc.)
storage-* # Storage provider drivers (S3, local filesystem)
queue-* # Queue provider driversWorkspace Configuration
Defined in pnpm-workspace.yaml:
packages:
- 'apps/*'
- 'packages/*'
- 'shared/*'
- 'drivers/*'Turborepo (turbo.json) orchestrates builds, ensuring correct dependency order across packages.
Deployable Services
Toast produces three independently deployable services:
| Service | Technology | Port | Docker Image |
|---|---|---|---|
| API | Hono + Node.js | 3000 | toast-api |
| Admin | React 19 + Vite + nginx | 5173 | toast-admin |
| Docs | Next.js + Fumadocs | 3300 | toast-docs |
All three services are built as Docker images in CI and deployed to Railway. See Self-Hosting for deployment details.
API Server Architecture
The API server follows a strict layered architecture. Requests flow through layers in order, and no layer may skip one below it.
Request Flow
Request
→ Global Middleware (requestId, logger, CORS)
→ Session Middleware (extracts user/session/siteId)
→ Route Handler
→ requireAuth / requirePermission middleware
→ Controller (validation, response shaping)
→ Service (business logic, events)
→ Repository (database queries via Drizzle)
→ Drizzle ORM → PostgreSQLGlobal Middleware Stack
Applied to every request in this order (defined in apps/api/src/app.ts):
| Order | Middleware | Path | Purpose |
|---|---|---|---|
| 1 | onError | * | Structured error responses (env-aware detail levels) |
| 2 | requestId | * | Assigns unique ID for log correlation |
| 3 | requestLogger | * | Logs method, path, status, duration |
| 4 | cors | * | CORS with admin origin allowlist |
| 5 | session | /api/* | Extracts user, session, siteId from cookies |
The session middleware is scoped to /api/* so health checks (/healthz) respond immediately without touching the database — critical for Railway deployment health probes.
Route Groups
Routes are mounted at their base paths in app.ts:
| Path | Module | Auth Required |
|---|---|---|
/healthz | healthRoutes | No |
/api/audit | auditRoutes | Yes |
/api/auth | authRoutes | Varies |
/api/capabilities | capabilitiesRoutes | No |
/api/collaboration | collaborationRoutes | Yes |
/api/content | contentRoutes | Yes |
/api/public/content | publicApiRoutes | No |
/api/settings | settingsRoutes | Yes |
/api/site | siteRoutes | No |
/api/users | usersRoutes | Yes |
/api/uploads | uploadRoutes | Yes |
Layer Responsibilities
Routes — OpenAPI schema definitions, middleware composition, and handler delegation. Each route file uses @hono/zod-openapi to define request/response schemas.
Services — Business logic, validation rules, and event emission. Services never access the database directly — they call repositories.
Repositories — Database queries via Drizzle ORM. Every query is scoped by siteId for multi-tenancy. See Database Patterns for conventions.
Multi-Tenancy
Every database table has a siteId foreign key with an index. Every repository method filters by siteId. This is a non-negotiable rule — see ADR-012: Unified User Model for the design rationale.
The siteId is extracted from the user's session by the session middleware and stored in Hono's context:
// In any route handler, siteId is available via context
const siteId = c.get('siteId');Authentication
Toast uses Better Auth for authentication. The auth configuration lives in apps/api/src/lib/auth.ts and supports:
- Email/password sign-in and sign-up
- Magic link authentication
- Session management with cookie-based tokens
See Authentication for integration details and RBAC Internals for the permission model.
Event System
Services emit domain events for side effects like audit logging. The event system uses a simple pub/sub pattern:
- Services emit events (e.g.,
content.created,settings.updated) - Subscribers handle side effects (e.g., writing audit log entries)
- Subscribers are registered at startup in
app.ts
See ADR-007: Event System for the design.
Driver System
External integrations (email, storage, queues) are abstracted behind driver interfaces. Each driver is an independent package in the drivers/ directory.
drivers/
email-mailgun/ # Mailgun email driver
storage-s3/ # AWS S3 / MinIO storage driver
...Drivers are loaded at startup based on environment variables. See Driver Configuration and Writing a Custom Driver.
Shared Packages
shared/contracts
TypeScript types, Zod schemas, and permission definitions shared between the API and admin panel. This package ensures type safety across the client-server boundary.
Key exports:
- Permission statements — defines what actions exist per resource
- Role presets — admin, editor, author, member
- Zod schemas — shared validation for API request/response types
packages/db
Drizzle ORM schema definitions, migration files, and the seed script. This package owns the database schema — no other package should define tables.
packages/ui
Shared React components built on Base UI. This is the single source of truth for UI primitives — apps must not create their own component libraries.
packages/config
Shared ESLint and TypeScript configuration. Keeps tooling consistent across all packages.
OpenAPI Documentation
The API auto-generates OpenAPI 3.1 specs from route definitions:
/docs/hono-openapi— Hono routes spec (internal)/docs/openapi— Merged spec (Hono + Better Auth routes)/docs— Interactive Scalar UI for exploring and testing endpoints
The merged spec is used to auto-generate the API Reference section of this documentation site.