Learn from OSS
Midday

Architecture Overview

Midday's system design — Supabase backend, Hono API, tRPC, banking integrations, and AI pipeline

Architecture Overview

For Product Managers

This page explains how Midday is structured as a software system. Focus on the diagrams and "Why It Matters" callouts to understand how bank connections, AI features, and invoicing work under the hood.

System Architecture Diagram

Midday uses a distributed services architecture — a Next.js dashboard, a Hono API server, and a BullMQ worker, backed by Supabase (managed PostgreSQL).

┌──────────────────────────────────────────────────────────────────┐
│                         USERS (Browser / Desktop)                │
└───────────────────────────────┬──────────────────────────────────┘

               ┌────────────────┼────────────────┐
               │                │                │
    ┌──────────▼──────┐  ┌─────▼──────┐  ┌──────▼───────┐
    │   Dashboard      │  │  Website   │  │  Desktop     │
    │  (Next.js 16)    │  │ (Next.js)  │  │  (Tauri 2)   │
    │  Railway :3001   │  │ Vercel     │  │  macOS app   │
    └────────┬─────────┘  └────────────┘  └──────┬───────┘
             │                                    │
             │  Server Actions + tRPC             │ REST API
             │                                    │
    ┌────────▼────────────────────────────────────▼───────┐
    │                  Hono API (Bun)                      │
    │                  Railway :3003                       │
    │                                                      │
    │  ┌──────────┐  ┌──────────┐  ┌──────────────────┐   │
    │  │  tRPC    │  │  REST    │  │  OpenAPI (Scalar) │   │
    │  │  Routers │  │  Routers │  │  Documentation    │   │
    │  └──────────┘  └──────────┘  └──────────────────┘   │
    └──────┬────────────┬──────────────┬──────────────────┘
           │            │              │
  ┌────────▼───┐  ┌─────▼─────┐  ┌────▼──────────────┐
  │  Supabase  │  │   Redis   │  │  External Services │
  │ (Postgres) │  │  (Cache)  │  │                    │
  │            │  │           │  │ • Plaid (banking)  │
  │ • Auth     │  │ • BullMQ  │  │ • GoCardless       │
  │ • Storage  │  │ • API cache│  │ • Teller           │
  │ • Realtime │  │ • Sessions│  │ • Stripe (payments)│
  │ • 45+ tables│  └─────┬─────┘  │ • OpenAI (AI)     │
  └────────────┘        │        │ • Resend (email)   │
                  ┌─────▼─────┐  │ • Xero/QuickBooks  │
                  │  Worker   │  │ • Typesense        │
                  │  (BullMQ) │  └────────────────────┘
                  │  Railway  │
                  │           │
                  │ • Bank sync│
                  │ • Documents│
                  │ • Inbox    │
                  │ • Invoices │
                  │ • Insights │
                  └───────────┘

Why It Matters

The Dashboard handles the UI and server-side rendering. The Hono API is a separate service providing tRPC + REST endpoints for the dashboard, desktop app, and third-party integrations. The Worker processes background jobs (bank sync, document processing, invoice scheduling) via BullMQ. All three services connect to the same Supabase PostgreSQL instance via Drizzle ORM.

API Architecture: Hono + tRPC

The API server (apps/api/) uses Hono as the HTTP framework and tRPC for type-safe RPC:

Hono Server (:3003)
  ├── /trpc/*           → tRPC routers (type-safe, dashboard uses these)
  └── /rest/*           → OpenAPI REST routers (desktop, third-party)
        ├── /transactions
        ├── /invoices
        ├── /customers
        ├── /bank-accounts
        ├── /tracker-projects
        ├── /tracker-entries
        ├── /inbox
        ├── /documents
        ├── /chat
        ├── /insights
        ├── /search
        ├── /reports
        ├── /tags
        ├── /users
        ├── /teams
        ├── /notifications
        ├── /files
        ├── /apps
        ├── /oauth
        ├── /webhooks
        ├── /mcp
        └── /transcription

Auth: JWT verification via JWKS (Supabase's public key endpoint) with HS256 fallback. Every request extracts { user: { id, email, full_name } } from the token.

Rate Limiting: hono-rate-limiter — 100 requests per 10 minutes per user.

Database Architecture

Supabase + Drizzle ORM

Midday uses Supabase (managed PostgreSQL) with Drizzle ORM for type-safe queries. The schema is defined in packages/db/src/schema.ts.

Core Entity Groups

Financial:

TablePurpose
transactionsBank transactions (amount, date, category, status)
transaction_categoriesCategory assignments
transaction_enrichmentsAI-enriched metadata (merchant, logo)
transaction_attachmentsLinked receipt files
transaction_tagsCustom tag assignments
bank_accountsConnected bank accounts
bank_connectionsProvider connections (Plaid/GoCardless/Teller)
institutionsBank/institution directory
exchange_ratesCurrency conversion rates

Invoicing:

TablePurpose
invoicesInvoice header (number, status, due date, amount)
invoice_productsLine items
invoice_recurringRecurring invoice schedules
invoice_templatesCustom invoice templates
invoice_commentsTeam collaboration on invoices

Time Tracking:

TablePurpose
tracker_projectsProjects being tracked
tracker_entriesTime log entries
tracker_reportsTime reports

Documents & Inbox:

TablePurpose
documentsStored files (vault)
document_tagsDocument categorization
inboxIncoming receipts/invoices
inbox_accountsConnected email accounts (Gmail/Outlook)

Users & Teams:

TablePurpose
usersUser profiles
teamsBusiness entities (tenancy unit)
users_on_teamTeam membership
customersClient directory

AI & Insights:

TablePurpose
insightsAI-generated financial analysis
transaction_category_embeddingsVector embeddings for categorization
document_tag_embeddingsVector embeddings for document classification

Banking Integration Architecture

The @midday/banking package abstracts four providers behind a unified interface:

@midday/banking
  ├── PlaidProvider       → US, Canada (Plaid SDK)
  ├── GoCardlessProvider  → EU, UK (REST via xior)
  ├── TellerProvider      → US (mTLS with certificates)
  └── EnableBankingProvider → EU, UK (JWT auth)

Each provider implements a common interface:

  • getAccounts() → list connected accounts
  • getTransactions(since) → fetch new transactions
  • getBalance() → current balance
  • disconnect() → revoke connection

Why Four Providers?

No single banking API covers the world. Plaid dominates the US/Canada market; GoCardless (via Nordigen) leads in Europe; Teller offers a US alternative with mTLS security; EnableBanking fills additional European coverage gaps. The abstraction layer means the rest of Midday's codebase doesn't care which provider supplies the data.

AI Architecture

Midday uses the Vercel AI SDK with multiple model providers:

AI Tools (Financial Assistant)

ToolPurpose
get-expensesQuery expense data by category, date, merchant
get-cash-flowCash in/out analysis over time
get-spendingSpending breakdown by category
get-burn-rateMonthly burn rate calculation
get-forecastRevenue/expense forecasting
get-metrics-breakdownKey financial metrics
get-invoice-payment-analysisInvoice payment patterns
web-searchExternal data lookup

AI Pipeline

User asks "What's my burn rate this quarter?"


AI SDK → selects tool: get-burn-rate


Tool executes: queries transactions table via Drizzle


Data returned to AI model


Model generates natural language response with data


Streamed to user via AI SDK React hooks

Document Intelligence

  • Azure Document Intelligence — OCR for receipt/invoice PDF extraction
  • Google AI embeddings — Vector embeddings for document classification
  • LangChain — Document processing pipeline (mammoth, officeparser, unpdf)

Authentication

MethodDetails
Email/PasswordVia Supabase Auth
OAuthGoogle, GitHub (via Supabase)
MFAMulti-factor authentication support
API KeysStored in api_keys table for programmatic access
OAuth AppsFull OAuth 2.0 server (authorization codes, access tokens)

What's Next