Learn from OSS
Midday

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

AppTechnologyPurpose
dashboardNext.js 16.1.6 (React 19)Main product — transactions, invoicing, time tracking, AI chat
apiHono 4.11.4 + tRPC 11.10.0API server — tRPC for dashboard, REST for desktop/third-party
workerBullMQ + HonoBackground processor — bank sync, documents, inbox, invoices
websiteNext.js 16.1.6Marketing site with MDX docs and AI chat
desktopTauri 2 + Vite 7 + React 19Native 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

ToolPurpose
Biome 2.4.4Linting + formatting (replaces ESLint + Prettier)
TypeScript 5.9.3Type checking across all packages
Bun testBuilt-in test runner (51 test files)

Dependency Map

Dashboard Dependencies

PackageVersionPurpose
Next.js16.1.6Full-stack React framework
React19.2.3UI framework
Zustand5.0.11Minimal global state
TanStack Query5.90.21Server-state caching
TanStack Table8.21.3Data tables
React Hook Form7.66.1Form management
nuqs2.8.8URL search params state
Recharts3.6.0Charts and data visualization
Framer Motion12.34.0Animations
@dnd-kitlatestDrag and drop
GeistlatestFont family
@sentry/nextjs10.xError tracking
ai + @ai-sdk/react5.0.87AI chat UI

API Dependencies

PackageVersionPurpose
Hono4.11.4HTTP framework
tRPC11.10.0Type-safe RPC
joselatestJWT verification (JWKS)
hono-rate-limiter0.4.2Rate limiting
@scalar/hono-api-referencelatestAPI documentation
ai + @ai-sdk/openai5.0.87AI assistant tools
Stripe20.1.0Payment processing
Polar0.45.1Subscription billing
@modelcontextprotocol/sdk1.0.0MCP integration

Infrastructure

ServicePurpose
Supabase (PostgreSQL)Primary database + auth + storage + realtime
Redis (Upstash)BullMQ queues + API cache
Cloudflare R2Institution logos and static assets
TypesenseFull-text search

Deployment

Production Architecture

ServicePlatformMethod
DashboardRailwayDocker (standalone Next.js on Bun)
APIRailwayDocker (Hono on Bun)
WorkerRailwayDocker (BullMQ on Bun)
WebsiteVercelServerless Next.js
JobsTrigger.devManaged task execution
Email previewVercelReact 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 Sentry

CI/CD Pipeline

Three GitHub Actions workflows:

WorkflowTriggerSteps
production.ymlPush to mainLint → Build → Typecheck → Test → Deploy (Railway/Vercel/Trigger.dev)
staging.ymlPush to non-mainSame validation → Deploy to staging environments
desktop.ymlManual dispatchTauri 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

CategoryVariables
SupabaseSUPABASE_URL, SUPABASE_SERVICE_KEY, SUPABASE_ANON_KEY
DatabaseDATABASE_URL + read replicas (FRA/SJC/IAD)
BankingPLAID_*, GOCARDLESS_*, TELLER_*, ENABLE_BANKING_*
PaymentsSTRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET
AIOPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY
EmailRESEND_API_KEY
SearchTYPESENSE_*
StorageR2_* credentials for Cloudflare R2
MonitoringSENTRY_*, OPENPANEL_*
JobsTRIGGER_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

ScopeToolFiles
UnitBun test runner51 .test.ts files
APIBun test runnertRPC router tests, REST router tests
PackagesBun test runnerbanking, invoice, import, documents, etc.

Tests use Bun's native test runner — no Jest, Vitest, or Playwright dependencies needed.

What's Next