Overview
BunShip is a monorepo built with Turborepo that separates concerns into apps (deployable services) and packages (shared libraries). Every package is written in TypeScript, and the entire stack runs on Bun for both development and production.Monorepo Structure
Package Descriptions
| Package | Path | Purpose |
|---|---|---|
| @bunship/config | packages/config/ | Centralized configuration — app settings, feature flags, billing plans, and RBAC permissions. Imported by every other package that needs runtime config. |
| @bunship/database | packages/database/ | Drizzle ORM schema definitions, migration scripts, and the database client factory. All tables (users, organizations, memberships, sessions, API keys, etc.) are defined here. |
| @bunship/utils | packages/utils/ | Shared error classes (AuthenticationError, ValidationError, NotFoundError), password strength validation, and general-purpose helpers. |
| @bunship/emails | packages/emails/ | React Email templates for transactional messages: verification, password reset, team invitations, and billing notifications. |
| @bunship/eden | packages/eden/ | A thin wrapper around Elysia’s Eden Treaty client that provides end-to-end type safety between the API and any TypeScript consumer. |
Package Dependency Graph
The dependency flow is intentionally one-directional. Packages at the bottom of the graph never import from packages above them.@bunship/eden depends on the API’s types, not its runtime code. This means your frontend
gets full autocomplete without bundling the server.Request Lifecycle
Every HTTP request to the API passes through a predictable middleware chain before reaching the route handler.Elysia receives the request
Bun’s HTTP server hands the request to Elysia, which parses the URL, method, headers, and body.
Global middleware runs
CORS, rate limiting, request logging, and body size validation are applied to all routes.
Auth middleware resolves the user
The
authMiddleware extracts the Bearer token from the Authorization header, verifies it with jose, and loads the user from the database.Organization middleware resolves the tenant
For organization-scoped routes (
/api/v1/organizations/:orgId/*), the organizationMiddleware loads the organization and the user’s membership in a single pass.Permission middleware checks RBAC
requirePermission() or requireRole() verifies the user’s role grants the specific permission needed for this operation.Route handler executes
The handler calls into a service function that contains the business logic. Services interact with the database through Drizzle and return plain objects.
Key Design Decisions
Bun-native runtime
Bun-native runtime
BunShip targets Bun exclusively rather than maintaining Node.js compatibility. This unlocks Bun’s native
crypto.subtle API, the built-in SQLite driver, faster startup times, and a single tool for runtime, package management, and test execution. The trade-off is that Bun must be available in your deployment environment.SQLite with Turso for production
SQLite with Turso for production
Instead of PostgreSQL, BunShip uses SQLite locally and Turso (a libSQL-based distributed SQLite service) in production. Benefits:
- Zero infrastructure for local development — the database is a file
- Edge replication through Turso for global low-latency reads
- Simpler operational model compared to managed PostgreSQL
- Full SQL support through Drizzle ORM
End-to-end type safety
End-to-end type safety
The API defines request/response schemas using Elysia’s TypeBox integration. These types flow through to:
- Route validation — Elysia rejects invalid payloads at the boundary
- Service layer — TypeScript enforces correct data shapes
- Database — Drizzle infers column types from the schema
- Client — Eden Treaty derives client types from the server’s route tree
Middleware composition over inheritance
Middleware composition over inheritance
Elysia plugins compose using Each middleware reads from and writes to the shared context (
.use(). BunShip chains middleware as independent Elysia instances:store), keeping individual pieces testable and replaceable.Configuration as code
Configuration as code
All feature flags, billing plans, role permissions, and app settings live in
@bunship/config as typed TypeScript objects. This means:- IDE autocomplete for every config value
- Compile-time errors when you reference a config key that does not exist
- No YAML/JSON parsing at runtime
- A single import (
@bunship/config) for any package that needs configuration
Application Configuration
The@bunship/config package exports four configuration modules:
- App Config
- Features Config
- Billing Config
- Permissions Config
Core application settings including the API prefix, JWT expiry times, CORS origins, and rate limits.
Database Layer
BunShip uses Drizzle ORM with SQLite. The schema is defined inpackages/database/src/schema/ with one file per table:
| Table | File | Description |
|---|---|---|
users | users.ts | User accounts, password hashes, 2FA secrets, lockout state |
sessions | sessions.ts | Refresh token hashes, IP address, user agent, expiry |
organizations | organizations.ts | Tenant workspaces with name, slug, settings |
memberships | memberships.ts | User-to-organization link with role assignment |
invitations | invitations.ts | Pending team invitations with token and expiry |
api_keys | apiKeys.ts | Scoped API keys per organization |
subscriptions | subscriptions.ts | Stripe subscription state per organization |
webhooks | webhooks.ts | Outgoing webhook endpoint configurations |
webhook_deliveries | webhookDeliveries.ts | Delivery attempts and status per webhook |
audit_logs | auditLogs.ts | Immutable activity log entries |
files | files.ts | Uploaded file metadata |
projects | projects.ts | Example resource table (replace with your domain) |
verification_tokens | verificationTokens.ts | Email verification and password reset tokens |
backup_codes | backupCodes.ts | Hashed 2FA recovery codes |

