Opinionated Bun + TypeScript API skeleton using Domain-Driven Design (bounded contexts, Either use cases, ports/adapters) and a production-shaped infra layer (Elysia, Drizzle/Postgres, JWT). Use it as a base for new HTTP services when you want clear boundaries and colocated tests—not a minimal “hello world.”
Shipped reference domain: identity (accounts, password hashing, POST /accounts, POST /sessions with access JWT + refresh cookie). Extend it and add sibling folders under src/domain/ for other product areas.
- Who this is for
- What you get
- Tech stack
- Architecture (high level)
- Project structure
- Quick start
- Environment variables
- Scripts
- API surface
- Working with bounded contexts
- Tests & CI
- Contributing
- Documentation
- License note
Teams or solo devs who want:
- Stable layering:
infra → domain → corewith explicit imports (seesrc/CLAUDE.md). - A working vertical slice: HTTP + validation + use case + repository + DB + auth-related patterns.
- Agent- and human-friendly docs: layered
CLAUDE.mdfiles plusdocs/archiqueture/.
- DDD layout by bounded context under
src/domain/<context>/(enterprise/+application/). - Application errors aligned with HTTP via
src/core/errorsand archstoneEitheruse cases. - Infra: Elysia app, OpenAPI at
/docs, Drizzle repositories, typed zod env, Bun hasher + JWT encrypters for identity ports. - Tests:
*.spec.tsnext to source;*.e2e-spec.tswith optional Postgres isolation (test/setup-e2e.ts). - Quality gate: Ultracite (Biome) + Husky pre-commit (
bun test+ format/lint).
| Layer | Choices |
|---|---|
| Runtime & tooling | Bun |
| HTTP | Elysia, @elysia/openapi, @elysia/cors, @elysia/jwt, @elysia/bearer |
| Domain primitives | archstone |
| Persistence | Drizzle ORM + PostgreSQL |
| Config | zod (src/infra/env) |
| Lint / format | Ultracite + Biome |
flowchart TB
subgraph infra [infra]
HTTP[HTTP controllers]
DB[Drizzle repositories]
Crypto[Hasher / JWT adapters]
end
subgraph domain [domain]
UC[Use cases]
ENT[Entities / VOs]
end
subgraph core [core]
ERR[AppError hierarchy]
end
HTTP --> UC
DB --> UC
Crypto --> UC
UC --> ENT
UC --> ERR
- Domain has no imports from
infra; core has no imports fromdomainorinfra. - Identity use cases depend on ports (
HashComparer,Encrypter,AccountRepository); infra implements them.
src/
core/ AppError + shared non-business primitives
domain/
identity/ reference BC: accounts, auth use cases, repo contract
infra/
app.ts Elysia + OpenAPI + /health
server.ts listen()
auth/ JWT bearer plugin (for protected routes)
cryptography/ BunHasher, JWT encrypter (implements domain ports)
env/ zod-validated process.env
http/ controllers, factories, presenters, http.module
database/ Drizzle client, schema, migrations, mappers, repos
test/
factories/ make-*.factory.ts
repositories/ in-memory-* (domain repo contracts)
cryptography/ fakes for port tests
setup-e2e.ts isolated schema + apply migrations (e2e preload)
run-e2e.ts discover *.e2e-spec.ts
docs/
archiqueture/ long-form architecture + onboarding (see index below)Deeper rationale: docs/archiqueture/domain-structure.md.
bun install
cp .env.example .env
# Start Postgres (or point DATABASE_URL at your instance)
docker compose up -d
bun run db:migrate
bun run dev- Dev server:
http://localhost:3333(override withPORT). - OpenAPI UI:
/docs. - Health:
HEAD /health.
Full setup (troubleshooting, E2E, production run): Getting started.
Forking for a real product: New API from this template.
Validated in src/infra/env/index.ts.
| Variable | Default | Notes |
|---|---|---|
NODE_ENV |
development |
development | production | test |
PORT |
3333 |
HTTP port |
DATABASE_URL |
(required) | Postgres URL |
JWT_ACCESS_SECRET |
(required) | Access token signing |
JWT_REFRESH_SECRET |
(required) | Refresh token signing (different secret) |
See Getting started — Environment for .env, E2E env, and CI.
| Command | Purpose |
|---|---|
bun run dev |
Watch mode API (src/infra/server.ts) |
bun run build |
Bundle to dist/server.js |
bun run start |
Run production bundle |
bun test |
Unit + use-case specs (*.spec.ts) |
bun run test:e2e |
Integration specs (*.e2e-spec.ts; needs Postgres) |
bun run db:generate |
Drizzle: generate SQL from schema |
bun run db:migrate |
Drizzle: apply migrations |
bun run db:studio |
Drizzle Studio |
bun run check |
Ultracite check |
bun run fix |
Ultracite fix |
Documented in OpenAPI (/docs). Reference implementation (identity):
| Method | Path | Notes |
|---|---|---|
POST |
/accounts |
Register account (name, username, email, password, optional slug) |
POST |
/sessions |
Email + password → { accessToken } + httpOnly refresh cookie (path: /auth/refresh) |
Details, code map, and failure behavior: docs/archiqueture/identity-bounded-context.md.
- Add a folder
src/domain/<your-context>/withenterprise/andapplication/(mirroridentitylayout). - Add
src/domain/<your-context>/CLAUDE.mdwhen non-trivial rules accumulate; link it fromsrc/domain/CLAUDE.md. - Implement repository interfaces in
src/infra/database/…; expose HTTP viasrc/infra/http/(controllers + factories). - Do not import one
src/domain/<a>/context from another—coordinate through infra or future application services.
There is no generic [bounded-context]/README.md scaffold file; follow src/domain/CLAUDE.md and docs/archiqueture/domain-structure.md.
- Unit / domain:
bun testpicks up*.spec.tsnext to sources. Useit("should …")for use cases and typical unit tests;*.vo.spec.tsusestest()without ashouldprefix on titles (test/CLAUDE.md). - E2E:
bun run test:e2euses.env.test+ isolated DB schema; E2E files usetest()(test/CLAUDE.md). - GitHub Actions (
.github/workflows/run-ci.yml): runs on pull requests tomainonly (merge is allowed only after green checks, so a second run onpushtomainis redundant). Jobs:check,bun test,test:e2ewith Postgres 17, Bun install cache,contents: readpermission.
For this template (and similar open-source setups), changes typically go through PRs into main with optional protected main. If you generated a new repo from the template, you can adopt a different Git workflow (e.g. direct pushes to main) and tune CI or branch rules—see CONTRIBUTING.md (“Your repo, your rules”). Security reports: SECURITY.md.
| Resource | Description |
|---|---|
CONTRIBUTING.md |
Optional OSS-style PR workflow; customizing Git/CI for template consumers |
SECURITY.md |
How to report security issues |
docs/archiqueture/README.md |
Index of architecture docs |
docs/archiqueture/getting-started.md |
Env, Docker, migrations, OpenAPI, common issues |
docs/archiqueture/new-project-from-template.md |
Checklist for new APIs from this repo |
docs/archiqueture/domain-structure.md |
Bounded context strategy |
docs/archiqueture/identity-bounded-context.md |
Identity BC reference |
CLAUDE.md |
Root conventions, commands, doc tier table |
src/CLAUDE.md |
Layer rules, “where code goes,” reading order |
Add a LICENSE file when you publish a derived project; this template does not ship one by default.