Architecture Overview
Full-stack TypeScript monorepo built on Bun. Backend serves the frontend as a SPA in production; in development both are served by the same Bun.serve() process with HMR.
Stack
| Layer | Technology | Notes |
|---|---|---|
| Runtime & bundler | Bun | Replaces Node + npm + webpack/Vite + Jest |
| HTTP server | Bun.serve() | No Express/Fastify; routes declared as plain object |
| Frontend framework | React 19 | StrictMode; functional components only |
| UI library | MUI v7 (Material UI) | Emotion styling engine |
| Client state | Redux Toolkit + RTK Query | RTK Query for server state; slices for client state |
| Routing (frontend) | React Router v7 | SPA client-side routing |
| ORM | Drizzle ORM | SQL-first; $inferSelect / $inferInsert types |
| Database | PostgreSQL | Accessed via postgres driver |
| Auth | jose JWT + HttpOnly cookies | HS256 access tokens; hashed refresh tokens in DB |
| Validation | Zod v4 | Shared schemas for request bodies |
| Testing | bun:test + happy-dom | Frontend: happy-dom DOM; backend: pure unit tests |
| E2E testing | Playwright | API tests + browser tests; Docker-isolated CI suite |
| Linting | oxlint + oxfmt | 50–100× faster than ESLint + Prettier |
| Dead code | knip | Detects unused exports and dependencies |
Request Flow
Browser / Client
│
▼
Bun.serve() ─────────────────────────────────────────────────
│ │
▼ ▼
/api/* routes /* (SPA fallback)
│ public/index.html (dev)
▼ dist/ (production)
withMiddleware()
├─ authMiddleware (verifies Bearer JWT)
├─ signupTokenMiddleware (verifies signup JWT for /set-password)
├─ authRateLimit (10 req/min per IP on auth endpoints)
└─ requireRole(...) (RBAC — admin | user)
│
▼
Controller
(parse req, validate body/params with Zod, call service)
│
▼
Service ──→ ErrorOr<T>
(business logic, strip sensitive fields, sign tokens)
│
▼
Repository
(Drizzle queries: SELECT / INSERT / UPDATE / DELETE)
│
▼
PostgreSQL
Directory Structure
bun-boiler/
├── backend/
│ ├── controllers/ HTTP layer — parse, validate, call service, return Response
│ ├── db/
│ │ ├── client.ts Singleton Drizzle connection; pingDb() with 5-attempt backoff
│ │ ├── migrations/ Generated SQL files — never edit manually
│ │ ├── schemas/ Source of truth for all DB types
│ │ └── seed.ts Idempotent admin user seed
│ ├── features/
│ │ ├── mail/ Optional Nodemailer SMTP (no-op when SMTP_HOST unset)
│ │ ├── telemetry/ Optional OTel traces + metrics + logs (no-op when OTEL_ENDPOINT unset)
│ │ └── validation/ Zod schemas + validateRequest / validateParam helpers
│ ├── middleware/ authMiddleware, signupTokenMiddleware, rateLimitMiddleware, requireRole
│ ├── repositories/ Drizzle queries — data access only, no business logic
│ ├── routes/ URL → handler mapping; spread into Bun.serve() routes
│ ├── services/ Business logic; returns ErrorOr<T>
│ ├── types/ Shared backend types (ErrorOr, AppError, AppJwtPayload, …)
│ ├── utils/
│ │ ├── auth/ JWT signing/verification, refresh token helpers, cookie builders
│ │ ├── response/ successResponse, serviceErrorResponse, unauthorizedError, …
│ │ └── test/ mockUserRepository, mockRefreshTokenRepository, mockUsers
│ └── ws/ WebSocket server handlers
├── frontend/
│ ├── features/ Self-contained feature modules (login, topNav, websocket, analytics)
│ ├── layout/ PageLayout shell + layout constants
│ ├── pages/ Page-level components (HomePage, LoginPage, NotFoundPage, …)
│ ├── providers/ React providers (AuthProvider, ThemeProvider, ToastProvider)
│ ├── redux/
│ │ ├── api/ RTK Query API slices (baseApi, authApi, integrationsApi)
│ │ ├── middleware/ localStorageMiddleware — persists theme + auth to localStorage
│ │ └── slices/ themeSlice
│ ├── shared/ Reusable components (skeletons, ErrorBoundary, ProtectedRoute, …)
│ └── telemetry/ Optional browser OTel fetch instrumentation
├── e2e/
│ ├── api/ Playwright API tests (no browser)
│ └── frontend/ Playwright browser tests (Chromium)
├── rest/ .http files for every endpoint (kulala.nvim / REST Client)
├── docs/ Obsidian vault — ADRs + technical docs
└── docker/ Dockerfiles + service configs
Path Aliases
| Alias | Resolves to | Use when |
|---|---|---|
@backend/* | ./backend/* | Crossing into the backend from anywhere |
@frontend/* | ./frontend/* | Crossing into the frontend from anywhere |
@type/* | ./types/* | Shared types (used in both layers) |
Relative imports are fine within the same layer. Never use ../../ across layer boundaries.
Dependency Injection
All controllers and services use factory functions. See ADR-003 for the full rationale.
userRepository ──injected into──▶ createUserService(repo)
│
userService ──injected into──▶ createUserController(service)
│
routes call userController.getUsers()
In tests: inject mockUserRepository into createUserService, inject the result into createUserController.
Optional Integrations
Both integrations are opt-in via environment variables. When the relevant env var is absent the module is a complete no-op — no SDK initialised, no overhead.
| Integration | Env var trigger | What it enables |
|---|---|---|
| OpenTelemetry | OTEL_ENDPOINT | Backend traces, metrics, structured logs via OTLP HTTP |
| OpenPanel | BUN_PUBLIC_OPENPANEL_CLIENT_ID | Frontend analytics and session replay |
| SMTP email | SMTP_HOST | Transactional email via Nodemailer |
Key Commands
bun dev # Dev server + HMR → http://localhost:3210
bun run build # Production bundle → dist/
bun test # Unit tests (backend + frontend)
bun run cc # Full quality gate: test + lint + format + knip
bun run db:generate # Generate migration SQL from schema changes
bun run db:migrate # Apply migrations to the database
bun run db:seed # Create initial admin user (idempotent)
bun run e2e:docker # Full E2E suite in isolated Docker stack