Arrbit is a production-ready Bun monorepo template for Arrbit projects. It provides a clean, extensible foundation with auth, admin, and observability built in — ready to be cloned and specialized for any domain.
- Bun monorepo workspace
- Elysia backend with
GET /health - OpenAPI docs at
/openapi - Drizzle ORM schema for the core SQLite tables (users, sessions, audit_logs)
- Drizzle Kit generated SQL migrations
- Bun-native
bun:sqlitemigration runner - First-boot admin account setup using Argon2id password hashing
- Auth API routes for login, logout, current user, and admin role checks
- Server-side sessions with hashed session tokens stored in SQLite
- HttpOnly SameSite session cookies with a production-secure cookie setting
- Failed-login rate limiting and auth/admin audit log entries
- Backend LogTape operational logging with redacted rotating JSONL output and local terminal mirroring
- React + Vite + TypeScript frontend
- Tailwind CSS v4 and shadcn/ui-compatible structure
- TanStack Query and TanStack Router frontend foundation
- Eden-based internal API client typing
- Shared TypeScript contracts
# Clone the template
git clone <arrbit-repo> my-new-project
cd my-new-project
# Customize for your project
# - Update APP_NAME in packages/shared/src/constants/app.ts
# - Extend the DB schema in apps/server/src/db/schema.ts
# - Change the project name in package.json files
# - Update the web app title in apps/web/index.html
# Install and run
bun install
bun run devInstall dependencies:
bun installStart the backend and frontend dev servers together:
bun run devThis uses a small Bun-native dev runner instead of Bun workspace --parallel mode, so the terminal is not prefixed with package runner labels. Routine Vite output is silenced; backend operational output in the shared dev terminal should come from LogTape. Fatal Vite startup diagnostics are still allowed through stderr so broken frontend startup is not hidden.
| App | URL | Runtime | Hot reload |
|---|---|---|---|
| Backend | http://localhost:3000 |
bun --hot apps/server |
Bun --hot (file watcher) |
| Frontend | http://localhost:5173 |
bunx --bun vite --logLevel error |
Vite HMR / React Fast Refresh |
| Typecheck | bun run dev:typecheck (manual) |
tsc --noEmit --watch |
TypeScript watch mode |
- Backend changes — Bun's
--hotreloads Elysia automatically. No app restart needed.
bun run checkRuns the focused code review suite in this order: Fallow audit, React Doctor diff scan for
apps/web, TypeScript typecheck, Bun tests, then Biome lint/format as the final cleanup
surface.
Use the full suite before handoff or review:
bun run check:quality:code:fullbun run check:code:quality:full is kept as an alias for the same full code-review suite.
Fallow owns repo-wide code intelligence (dead code, dependency health, duplication,
complexity, boundaries). React Doctor owns React-specific diagnostics only and has its own
apps/web/doctor.config.json with React Doctor dead-code analysis disabled so it does not
overlap with Fallow. Biome runs last for formatting and linting. See
docs/architecture/code-quality-suite.md for the detailed tool split, ignore policy, and
official source links.
- Frontend changes — Vite HMR updates the browser instantly. No page reload needed.
- TypeScript contract changes — Run
bun run dev:typecheckin a separate terminal for live type-checking across the workspace. - API calls — The frontend client uses the browser's current origin. Vite proxies
/healthand/apito the backend viahttp://localhost:3000.
Run each dev server manually:
bun run dev:server # Backend on http://localhost:3000
bun run dev:web # Frontend on http://localhost:5173
bun run dev:typecheck # TypeScript watchTests that need a server port default to 3001, separate from development port 3000.
Useful checks:
bun test
bun run typecheck
bun run build
bun run check
bun run check:quality:code:fullDatabase commands:
bun run db:generate
bun run db:migratedb:generate uses Drizzle Kit to generate SQL migrations. db:migrate follows Bun's Drizzle guide by applying those generated migrations from a Bun script with Drizzle's Bun SQLite migrator, so the app keeps using native bun:sqlite.
SQLite database paths are fixed by environment:
- Development database:
data/db/arrbit-dev.sqlite. - Test database:
data/db/arrbit-test.sqlite.
Tests always set and validate the canonical test database path before app code loads. They reset data/db/arrbit-test.sqlite and its SQLite sidecars deliberately, run the real migrations, and never fall back to the development database.
SQLite does not start a separate database listener, so test isolation is enforced with the canonical test database file plus the isolated test server port.
The backend configures LogTape once at startup before migrations and request handling. Application, request, security, and Drizzle query logs always share one operational JSONL file sink. In local development they are also mirrored to the terminal with LogTape's ANSI console formatter, so bun dev shows backend startup and request logs without Bun workspace prefixes or routine Vite output.
- Default path:
data/logs/arrbit.jsonl. - Default rotation: 10 MiB per file, keeping 5 rotated files.
- Default levels:
debugin local development,fatalin tests, andinfoin production. - Terminal app logs: enabled in local development, disabled by default in tests and production, override with
LOG_CONSOLE=true|false. - LogTape meta warnings/errors use a separate console sink so logging-system issues stay visible.
Optional overrides:
LOG_LEVEL=info
LOG_FILE_PATH=data/logs/arrbit.jsonl
LOG_FILE_MAX_SIZE_BYTES=10485760
LOG_FILE_MAX_FILES=5
LOG_CONSOLE=trueLog output is redacted with both structured-field and formatted-message redaction. Do not intentionally log passwords, raw session tokens, cookies, authorization headers, CSRF tokens, or password hashes. The SQLite auditLogs table remains the durable security audit trail for auth/admin events; LogTape is for operational diagnostics. Frontend/browser LogTape logging is not included in this slice.
Auth endpoints:
GET /api/auth/setup
POST /api/auth/setup
POST /api/auth/login
POST /api/auth/logout
GET /api/auth/me
GET /api/admin/auth/checkOn a fresh database, open the app and create the first account from the login page. That first account is created as the admin account and signed in automatically. After any user exists, the setup endpoint is disabled.
- Frontend:
http://localhost:5173 - Backend:
http://localhost:3000returns an API landing response with links to the frontend, health check, and OpenAPI UI - Health:
http://localhost:5173/healthvia Vite proxy, orhttp://localhost:3000/healthdirectly - OpenAPI UI:
http://localhost:3000/openapi
Copy .env.example to .env for local development.
DATABASE_URLdefaults todata/db/arrbit-dev.sqliteoutside tests.- In
NODE_ENV=test,DATABASE_URLmust bedata/db/arrbit-test.sqlite; unsafe test settings fail loudly instead of falling back to the dev database. BACKEND_ORIGINis the backend target Vite proxies to during development.VITE_API_BASE_URLis blank by default so the frontend uses the Vite dev proxy; set it only when serving the frontend separately from the API.USE_POLLING=trueenables polling file watchers for environments where native file watching is unreliable.SESSION_COOKIE_SECUREisfalsein local HTTP development; set it totruebehind HTTPS in production.LOG_LEVELcontrols backend operational log verbosity. Valid values aretrace,debug,info,warning,error, andfatal.LOG_FILE_PATHdefaults todata/logs/arrbit.jsonlfor the backend rotating JSONL log file.LOG_FILE_MAX_SIZE_BYTESdefaults to10485760bytes before rotation.LOG_FILE_MAX_FILESdefaults to5retained log files.LOG_CONSOLEmirrors backend app logs to the terminal. It defaults totruein local development andfalsein tests and production.
Pushes to main publish prvctech/arrbit:latest. Tag pushes such as v1.2.3 publish matching version tags. Use a pinned sha-* or semver tag when you want an immutable rollback target.
The image runs one Bun server on container port 3000. That server serves the built frontend and the API from the same origin. Production does not run a Vite dev or preview server inside the container.
Runtime defaults in the image:
SERVER_PORT=3000WEB_ORIGIN=http://localhost:3000FRONTEND_DIST_ROOT=/app/apps/web/distDATABASE_URL=/app/data/db/arrbit.sqliteHELP_TICKET_STORAGE_ROOT=/app/data/media/ticketLOG_FILE_PATH=/app/data/logs/arrbit.jsonlSESSION_COOKIE_SECURE=true
Mount /app/data to persistent storage. For LAN-only HTTP, set WEB_ORIGIN to the real HTTP URL and override SESSION_COOKIE_SECURE=false. For HTTPS behind a reverse proxy, set WEB_ORIGIN to the public HTTPS URL and keep SESSION_COOKIE_SECURE=true.
Deployment docs:
- Docker guide:
docs/deployment/docker.md - Unraid guide:
docs/deployment/unraid.md