How Midday Is Built
Repository structure, Bun + Turborepo build system, dependencies, testing, and deployment
How Midday Is Built
This page covers the practical engineering behind Midday: how the monorepo is organized, what tools orchestrate the build, what dependencies power the features, and how the product gets deployed.
For Product Managers
This page answers "how does the engineering team ship Midday?" — the tools, processes, and infrastructure that turn source code into a running fintech product. Understanding this helps you estimate customization effort and deployment complexity.
Repository Structure
Midday is a Bun monorepo with Turborepo for build orchestration. 5 apps and 27 packages live in a single repository.
midday/
├── apps/
│ ├── dashboard/ ← Next.js 16 — main product UI (port 3001)
│ ├── api/ ← Hono + tRPC — API server (port 3003)
│ ├── worker/ ← BullMQ — background job processor
│ ├── website/ ← Next.js — marketing site (port 3000)
│ └── desktop/ ← Tauri 2 — native macOS app
├── packages/
│ ├── db/ ← Drizzle ORM schema + 36 migrations
│ ├── supabase/ ← Supabase client, types, helpers
│ ├── ui/ ← Radix + Tailwind component library
│ ├── banking/ ← Plaid + GoCardless + Teller + EnableBanking
│ ├── invoice/ ← PDF generation, templates, calculations
│ ├── accounting/ ← Xero + QuickBooks + Fortnox sync
│ ├── inbox/ ← Gmail + Outlook email sync
│ ├── documents/ ← OCR, classification, embeddings
│ ├── insights/ ← AI financial analysis
│ ├── email/ ← 18 React Email templates
│ ├── notifications/ ← Resend delivery
│ ├── trpc/ ← tRPC client/server setup
│ ├── cache/ ← Redis caching layer
│ ├── jobs/ ← Trigger.dev task definitions
│ ├── job-client/ ← BullMQ queue client
│ ├── events/ ← OpenPanel analytics
│ ├── categories/ ← Transaction categorization + tax rates
│ ├── customers/ ← Customer enrichment (Exa AI)
│ ├── import/ ← Transaction CSV import
│ ├── plans/ ← Subscription plan definitions (Polar)
│ ├── app-store/ ← Integration apps (Slack, WhatsApp, etc.)
│ ├── location/ ← Countries, currencies, timezones
│ ├── encryption/ ← Encryption utilities (jose)
│ ├── health/ ← Health check probes
│ ├── logger/ ← Pino structured logging
│ ├── utils/ ← Shared utilities
│ ├── desktop-client/ ← Tauri client utilities
│ └── tsconfig/ ← Shared TypeScript config
├── turbo.json ← Turborepo task configuration
└── package.json ← Root config (Bun 1.3.10)The 5 Applications
| App | Technology | Purpose |
|---|---|---|
| dashboard | Next.js 16.1.6 (React 19) | Main product — transactions, invoicing, time tracking, AI chat |
| api | Hono 4.11.4 + tRPC 11.10.0 | API server — tRPC for dashboard, REST for desktop/third-party |
| worker | BullMQ + Hono | Background processor — bank sync, documents, inbox, invoices |
| website | Next.js 16.1.6 | Marketing site with MDX docs and AI chat |
| desktop | Tauri 2 + Vite 7 + React 19 | Native macOS app with deep links and file system |
Why Bun?
Midday uses Bun instead of Node.js as the JavaScript runtime. Bun offers significantly faster startup times (important for serverless/Railway), native TypeScript execution (no transpilation step), a built-in test runner (replacing Jest/Vitest), and faster package installation. The Dockerfiles use oven/bun:1.3.10 as the base image.
Build System
Turborepo Pipeline
bun install ← installs all dependencies (Bun workspaces)
│
▼
turbo build ← orchestrates parallel builds
│
├──► @midday/db (Drizzle schema → types)
├──► @midday/ui (component library → bundled)
├──► @midday/trpc (tRPC config → types)
│
├──► dashboard (Next.js build → standalone output)
├──► api (Bun bundle → dist/)
├──► worker (Bun bundle → dist/ + workbench UI)
└──► website (Next.js build → standalone output)Code Quality
| Tool | Purpose |
|---|---|
| Biome 2.4.4 | Linting + formatting (replaces ESLint + Prettier) |
| TypeScript 5.9.3 | Type checking across all packages |
| Bun test | Built-in test runner (51 test files) |
Dependency Map
Dashboard Dependencies
| Package | Version | Purpose |
|---|---|---|
| Next.js | 16.1.6 | Full-stack React framework |
| React | 19.2.3 | UI framework |
| Zustand | 5.0.11 | Minimal global state |
| TanStack Query | 5.90.21 | Server-state caching |
| TanStack Table | 8.21.3 | Data tables |
| React Hook Form | 7.66.1 | Form management |
| nuqs | 2.8.8 | URL search params state |
| Recharts | 3.6.0 | Charts and data visualization |
| Framer Motion | 12.34.0 | Animations |
| @dnd-kit | latest | Drag and drop |
| Geist | latest | Font family |
| @sentry/nextjs | 10.x | Error tracking |
| ai + @ai-sdk/react | 5.0.87 | AI chat UI |
API Dependencies
| Package | Version | Purpose |
|---|---|---|
| Hono | 4.11.4 | HTTP framework |
| tRPC | 11.10.0 | Type-safe RPC |
| jose | latest | JWT verification (JWKS) |
| hono-rate-limiter | 0.4.2 | Rate limiting |
| @scalar/hono-api-reference | latest | API documentation |
| ai + @ai-sdk/openai | 5.0.87 | AI assistant tools |
| Stripe | 20.1.0 | Payment processing |
| Polar | 0.45.1 | Subscription billing |
| @modelcontextprotocol/sdk | 1.0.0 | MCP integration |
Infrastructure
| Service | Purpose |
|---|---|
| Supabase (PostgreSQL) | Primary database + auth + storage + realtime |
| Redis (Upstash) | BullMQ queues + API cache |
| Cloudflare R2 | Institution logos and static assets |
| Typesense | Full-text search |
Deployment
Production Architecture
| Service | Platform | Method |
|---|---|---|
| Dashboard | Railway | Docker (standalone Next.js on Bun) |
| API | Railway | Docker (Hono on Bun) |
| Worker | Railway | Docker (BullMQ on Bun) |
| Website | Vercel | Serverless Next.js |
| Jobs | Trigger.dev | Managed task execution |
| Email preview | Vercel | React Email dev server |
Docker Images
All three Railway services use oven/bun:1.3.10 as the base:
# Example: apps/dashboard/Dockerfile
FROM oven/bun:1.3.10 AS base
→ turbo prune @midday/dashboard
→ bun install
→ bun run build
→ Standalone Next.js output
→ PORT from Railway, GIT_COMMIT_SHA for SentryCI/CD Pipeline
Three GitHub Actions workflows:
| Workflow | Trigger | Steps |
|---|---|---|
| production.yml | Push to main | Lint → Build → Typecheck → Test → Deploy (Railway/Vercel/Trigger.dev) |
| staging.yml | Push to non-main | Same validation → Deploy to staging environments |
| desktop.yml | Manual dispatch | Tauri build (macOS aarch64 + x86_64) → Code sign → Notarize → GitHub Release |
Smart deploys: turbo build --affected detects which services changed and only deploys those.
Key Environment Variables
| Category | Variables |
|---|---|
| Supabase | SUPABASE_URL, SUPABASE_SERVICE_KEY, SUPABASE_ANON_KEY |
| Database | DATABASE_URL + read replicas (FRA/SJC/IAD) |
| Banking | PLAID_*, GOCARDLESS_*, TELLER_*, ENABLE_BANKING_* |
| Payments | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET |
| AI | OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY |
RESEND_API_KEY | |
| Search | TYPESENSE_* |
| Storage | R2_* credentials for Cloudflare R2 |
| Monitoring | SENTRY_*, OPENPANEL_* |
| Jobs | TRIGGER_SECRET_KEY, REDIS_QUEUE_URL |
Background Job Systems
Midday uses two background job systems:
Trigger.dev (Scheduled Tasks)
Trigger.dev handles jobs that need scheduling, retries, and observability:
- Bank sync polling
- Invoice recurring scheduler
- Document processing pipeline
- Inbox email sync
- Notification delivery
- Team onboarding sequences
BullMQ (Queue-Based Jobs)
BullMQ handles high-throughput queued work:
- Transaction import batches
- Inbox provider sync
- Notification queuing
- Document processing queue
The Worker app (apps/worker/) includes a Workbench admin dashboard (accessible at /admin) for monitoring queue health.
Testing
| Scope | Tool | Files |
|---|---|---|
| Unit | Bun test runner | 51 .test.ts files |
| API | Bun test runner | tRPC router tests, REST router tests |
| Packages | Bun test runner | banking, invoice, import, documents, etc. |
Tests use Bun's native test runner — no Jest, Vitest, or Playwright dependencies needed.