Monorepo structure
The codebase is organized into apps and packages:Apps
apps/server - Hono HTTP server
- Entry point that wires everything together
- Mounts RPC handler, OpenAPI docs, auth endpoints, and Bull Board dashboard
- Runs database migrations on startup
- Registers BullMQ workers as side effects
apps/web - TanStack Start/React frontend
- Server-side rendered React application
- TanStack Router for file-based routing
- Vite for development and bundling
- Runs on port 3001 in development
Packages
packages/api - Business logic layer
- oRPC routers for type-safe RPC procedures
- Service and repository layers
- BullMQ workers for background jobs
- Elasticsearch client for search
packages/auth - Authentication
- better-auth instance with email/password authentication
- Organizations plugin for multi-tenancy
- Admin plugin for role-based access control
packages/db - Database layer
- Drizzle ORM schema definitions
- PostgreSQL client
- Migration runner
- Seed data
packages/env - Environment validation
- Environment variable validation using
@t3-oss/env-coreand Zod - Separate configs for server and web
packages/config - Shared configuration
- TypeScript compiler configuration
- Build tool configuration
API layer with oRPC
Nanahoshi uses oRPC for type-safe remote procedure calls between the frontend and backend.Procedure builders
Base procedures are defined inpackages/api/src/index.ts:
Context creation
Context is created for each request inpackages/api/src/context.ts:
Router composition
Routers are composed inpackages/api/src/routers/index.ts:
Domain structure
Each domain follows the pattern:*.router.ts- oRPC procedures with input validation (Zod schemas)*.service.ts- Business logic and orchestration*.repository.ts- Database queries using Drizzle ORM*.model.ts- TypeScript types and Zod schemas
packages/api/src/routers/books/book.router.ts):
Server application
The Hono server (apps/server/src/index.ts) mounts several handlers:
Route structure
/rpc/*- oRPC RPC handler (used by frontend)/api-reference/*- OpenAPI reference documentation/api/auth/*- better-auth authentication endpoints/admin/queues/- Bull Board dashboard for monitoring BullMQ queues/download/:uuid- Signed URL file downloads/reader/*- TTU ebook reader static files
Handler setup
Startup sequence
- Run database migrations:
await runMigrations() - Run first-time seed:
await firstSeed() - Ensure Elasticsearch index exists:
await ensureIndex() - Import worker modules (side effect registration)
- Schedule cron jobs:
scheduleBookIndex() - Start HTTP server
React frontend
The web app (apps/web) uses TanStack Start with TanStack Router for file-based routing.
oRPC client setup
The oRPC client is wired into TanStack Query inapps/web/src/utils/orpc.ts:
orpc.<router>.<procedure>.queryOptions()- For TanStack Query queriesorpc.<router>.<procedure>.mutate()- For mutations- Full type safety from backend to frontend
Route context
Routes receive{ orpc, queryClient } in their context. Auth guards use beforeLoad to check session and redirect:
Database with Drizzle ORM
Nanahoshi uses Drizzle ORM with PostgreSQL.Schema organization
Schema is split into two files:packages/db/src/schema/general.ts - Application tables:
book,book_metadata,library,library_pathauthor,series,publishercollection,collection_bookliked_book,reading_progress,activityscanned_file,app_settings
packages/db/src/schema/auth.ts - better-auth tables:
user,session,account,verificationorganization,member,invitationapikey
Migration workflow
- Edit schema files
- Generate migration:
bun run db:generate - Commit the new migration file
- Server applies it automatically on next startup via
runMigrations()
packages/db/src/migrations/.
Database client
The Drizzle client is created inpackages/db/src/index.ts:
Elasticsearch for search
Nanahoshi uses Elasticsearch for full-text search with Japanese language support.Client setup
The client is initialized inpackages/api/src/infrastructure/search/elasticsearch/search.client.ts:
Index management
The index schema is defined in JSON with custom analyzers:- Sudachi analyzer - Japanese text tokenization and analysis
- romaji analyzer - For romanized Japanese text
- Custom mappings for book fields
ensureIndex() checks if the index exists and if the schema changed. If the hash doesn’t match, the index is recreated automatically.
Search operations
BullMQ workers
Background jobs are processed by BullMQ workers backed by Redis.Queue definitions
Three queues are defined inpackages/api/src/infrastructure/queue/queues/:
file-event.queue.ts
- Processes file add/delete events
- Creates book records
- Triggers metadata enrichment
book-index.queue.ts
- Indexes books into Elasticsearch
- Handles bulk reindexing
cover-color.queue.ts
- Extracts dominant color from book covers
- Updates book metadata
Worker registration
Workers are imported as side effects inapps/server/src/index.ts:
Worker instance that processes jobs:
Monitoring
Bull Board provides a web UI at/admin/queues/ to:
- View queue status and job counts
- Inspect individual jobs
- Retry failed jobs
- Clear queues
Infrastructure
Nanahoshi requires several infrastructure services:PostgreSQL
- Primary database for relational data
- Uses groonga/pgroonga image for full-text search support
- Exposed on port 5432 in development
Redis
- BullMQ job queue backend
- Session storage (if configured)
- Exposed on port 6379 in development
Elasticsearch
- Full-text search engine
- Japanese text analysis with Sudachi tokenizer
- Exposed on port 9200 in development
Development setup
Infrastructure is managed via Docker Compose:apps/server/.env.
Package management
Nanahoshi uses Bun as the package manager and runtime.Workspace aliases
Packages reference each other viaworkspace:* protocol:
Catalog dependencies
Shared dependency versions are defined in the rootpackage.json: