Overview
Hazel Chat is organized as a monorepo using Bun workspaces and Turborepo for build orchestration. This structure enables code sharing, consistent tooling, and efficient builds across multiple applications and packages.Directory Structure
Applications (apps/)
Each application is independently deployable but shares common packages.
Web App (apps/web/)
The React frontend application.
The web app uses TanStack Router for file-based routing. Each file in
routes/ becomes a route automatically.Backend API (apps/backend/)
The Effect-TS API server handling business logic.
- Services use
Effect.Servicefor dependency injection - Policies implement row-level security
- RPC handlers map to domain contracts
Cluster Service (apps/cluster/)
The distributed workflow execution service.
- Message notifications
- GitHub webhook processing
- File cleanup
- RSS feed polling
- AI thread naming
Electric Proxy (apps/electric-proxy/)
Cloudflare Worker that enforces authorization for Electric SQL sync.
When adding a new Electric-synced table, you must update both
ALLOWED_TABLES and getWhereClauseForTable in the proxy.Packages (packages/)
Shared code used across multiple applications.
Database (packages/db/)
Central package for database schema and queries.
- Drizzle ORM with PostgreSQL
- Automatic transaction context propagation
- Policy-based authorization
- Type-safe queries
Domain (packages/domain/)
Shared contracts and types between frontend and backend.
Why Domain Package?
Ensures frontend and backend always agree on API contracts through shared TypeScript types.
Type Safety
Changes to RPC contracts are caught at compile-time, preventing runtime errors.
Backend Core (packages/backend-core/)
Reusable backend services and repositories.
insert(),update(),deleteById()findById(),with()- Custom query methods
Schema (packages/schema/)
Branded ID types for type safety.
OrganizationId, ChannelId, UserId, MessageId, BotId, and many more.
Dependency Graph
The
domain and schema packages are foundational - they have minimal dependencies and are imported by both frontend and backend.Turborepo Configuration
Task Pipeline
Turborepo orchestrates build tasks across the monorepo:build- Compile TypeScript and bundle assetstypecheck- Run TypeScript compiler without emittingdev- Start development server (persistent)test- Run unit and integration tests
Running Tasks
Turborepo automatically determines which packages need to be built based on the dependency graph.
Workspace Configuration
Bun Workspaces
Package Naming Convention
@hazel/*- Internal packages (e.g.,@hazel/db,@hazel/domain)- Applications use descriptive names without scope
Import Best Practices
Use Correct Package Imports
Package Import Rules
Frontend
Can import:
@hazel/domain, @hazel/schema, @hazel/uiBackend
Can import: All packages including
@hazel/db, @hazel/backend-coreDomain
Can import: Only
@hazel/schema (minimal dependencies)Schema
No dependencies (foundational package)
Adding New Packages
- Create package directory:
- Create
package.json:
- Create
src/index.ts:
- Install dependencies:
- Import in other packages:
Build Output
Development
In development, TypeScript files are executed directly by Bun (no build step needed).Production
For production, each app builds to adist/ directory:
Benefits of This Structure
Code Sharing
Shared packages eliminate duplication
Type Safety
TypeScript types shared between apps
Fast Builds
Turborepo caches and parallelizes builds
Easy Refactoring
Changes propagate automatically
Consistent Tooling
Same tools across all packages
Isolated Testing
Test packages independently
Next Steps
Effect-TS Patterns
Learn about Effect-TS usage in the backend
Database Package
Explore the database layer and transaction patterns