Tavern Cozy is deliberately built as a monolith with a local database. The web app, the API, and the database all live on the same machine — the web app proxies /api to the API process, and the API reads and writes one local SQLite file. There are no microservices, no remote database, no cross-network hops in the request path.
That design is what keeps it fast: every request stays on the box. This page documents the stack for the record.
The stack at a glance
Runtime — Bun. Both the API and the web app run on Bun, in dev and in production. One runtime, no build-tool friction, and native-speed SQLite bindings.
Web — TanStack Start. Full-stack React on Vite. TanStack Router drives routing, TanStack Query drives data, SSR is served by Nitro (bun preset).
Chat — TanStack AI. AI chat via @tanstack/ai with the OpenRouter adapter. Messages stream as SSE straight to the browser.
API — Elysia 2. A Bun-native web framework. Type-safe routes, middleware, and guards on the /api surface.
Auth — Better Auth. Email/password, Google OAuth, passkeys, multi-session account switching, admin roles, and Turnstile captcha — all server-side.
Data — Drizzle ORM on SQLite. bun:sqlite reads and writes one file. Drizzle gives typed queries, migrations, and a schema.
Sounds — cuelume. Interaction sounds synthesized live in the browser via Web Audio (17-sound palette, zero audio files). A per-user toggle persists to the profiles table; buttons, nav, modals, toggles, and chat events each map to a context-appropriate sound.
Why a monolith with local SQLite
- Zero latency by construction. The API and the database are on the same host; a query is a local file read, not a network call. No DB connection pool to tune, no remote round-trip to hide.
- One deploy, one box. Web + API + SQLite ship together. Deploys are a git pull and a process restart.
- Simple to reason about. No distributed-system failure modes: if the process is up, the data is there.
- Fast to develop. The whole stack boots from a single
buncommand — no orchestrator, no containers, no service wiring.
How the pieces talk
Browser
│ HTTPS (Cloudflare edge)
▼
nginx ──► tavern-start (TanStack Start, Nitro, :3000)
│ proxies /api/** (plus OAuth redirect passthrough)
▼
tavern-elysia (Elysia 2, Better Auth, :3001)
│ reads/writes one local file
▼
data/tavern.db (SQLite, WAL mode)Chat streams are SSE: tavern-elysia streams tokens through the /api proxy to the browser, with no intermediate service.
The details
Runtime & language
- Bun — the single runtime for both servers and the build tooling. Bun canary is used in dev, matching the dependency policy below.
- TypeScript everywhere — web app, API, schema, and tests.
- Dependency policy — always the latest release of every library, including beta/canary dist-tags (e.g. Bun canary, Elysia’s
experimentaltag, TanStack 1.x canary line). No pinning to stable-only; patches land as they publish.
Web app (tavern-start)
- TanStack Start — full-stack React framework (Vite-based, Nitro SSR with the
bunpreset). - TanStack Router — typed file-based routing.
- TanStack Query — server state, caching, and invalidation.
- Tailwind CSS — styling, with shadcn-style UI primitives built on Base UI.
- cuelume — interaction sounds (Web Audio, no audio files); per-user enable/disable saved to
profiles.soundsEnabledand applied app-wide at the root shell. - Paraglide (inlang) — i18n (English + Spanish).
- Expo / React Native — the mobile app, built with EAS, Google sign-in, RevenueCat subscriptions.
API (tavern-elysia)
- Elysia 2 — Bun-native HTTP framework with typed context and plugins.
- Drizzle ORM — typed SQL queries against SQLite.
- Better Auth — the full auth surface: email/password + username, Google OAuth, passkeys, multi-session, admin roles, Turnstile captcha.
- OpenRouter — LLM + image-model access via
@tanstack/aiadapters. - R2 — object storage for uploaded images (the one cloud dependency, used only for media).
Database
- SQLite via
bun:sqlite, a single file (data/tavern.db) in WAL mode with foreign keys on and a busy timeout. - Drizzle manages the schema, and the boot path auto-reconciles missing columns so old databases upgrade in place.
Repository layout
tavern-workspace/
├── tavern-start/ # TanStack Start web app (Vite + Nitro)
├── tavern-elysia/ # Elysia 2 API (Bun) + SQLite
├── tavern-expo/ # Expo / React Native mobile app
└── tavern-docs/ # this documentation site (Astro)For running each service, see the AGENT.md in each package — that’s the source of truth for commands.