Skip to main content

ADR-001 — Backend stack: Fastify + Drizzle + Postgres

canonical · tech-lead · updated 2026-06-28 · source

  • Status: Accepted
  • Date: 2026-05-20
  • Owner: Tech Lead
  • Related issue: #6 (repo scaffold + ADRs)

Context

  • Tu Empresa Digital OS needs a backend that hosts auth, integrations, AI run state, push fan-out, and realtime updates back to the desktop client.
  • Locked product constraints (knowledge/tedos-product-decisions.md): macOS-only MVP, single tier, Web Push + APNs for nativo, in-app realtime updates.
  • DevOps constraint (knowledge/tedos-devops-plan.md): self-hosted on DigitalOcean — no Supabase, no managed Redis. Background jobs must run inside the same Postgres dependency.
  • Required runtime capabilities:
    • WebSockets (sidecars, design previews, browser tools, multiplayer status)
    • Server-Sent Events for in-app notifications and AI streaming
    • Background workers (no Redis dep → must run on Postgres)
    • Web Push + APNs delivery for nativo notifications
    • TypeScript end-to-end with packages/shared

Decision

  • Use Fastify as the HTTP/WebSocket framework.
  • Use @fastify/websocket for WS endpoints; native SSE via Fastify reply streaming for in-app updates.
  • Use Drizzle ORM over Postgres (DO Managed Postgres) as the single source of truth.
  • Use pg-boss for background workers (queues, retries, cron) — runs on Postgres, removes Redis dependency.
  • Run Web Push (VAPID) and APNs delivery as pg-boss workers consuming the same DB.
  • Co-locate types in packages/shared (@tedos/shared), import on both desktop and api.

Out of scope here: authentication strategy (separate ADR), schema migrations workflow, multi-region.

Consequences

Positive

  • Single Postgres dependency — DO Managed Postgres covers DB, queues, push retries.
  • Drizzle gives raw-SQL feel + TS types, zero codegen surprise vs Prisma.
  • Fastify ecosystem mature for WS, hooks, plugins; ~2x Express throughput.
  • Workers, SSE, WS all share the same Fastify process for MVP; can split per-role later.

Negative / trade-offs

  • pg-boss less battle-tested than BullMQ/Redis under high throughput — fine for MVP (~10k jobs/day).
  • No Hono / edge-runtime portability — locks us to Node 20 runtime on a DO droplet.
  • Drizzle migration tooling is younger than Prisma; we accept the rougher edges for the simpler runtime story.

Follow-ups required

  • AI Eng (#8) wires SSE for AI runs.
  • Tauri Eng (#7) wires WS client + reconnect.
  • DevOps writes the migration runner + DO deployment workflow.

Alternatives considered

OptionWhy not
Hono + Drizzle + PostgresHono is faster on edge but WS plugin is less mature than @fastify/websocket; we are not deploying to edge.
Bun + Elysia + DrizzleBun runtime still has rough edges with native deps (node-postgres, apn); maturity risk for MVP.
Express + Prisma + Postgres + Redis (BullMQ)Adds a Redis dep we explicitly want to avoid (DevOps cost + ops surface). Express is the slowest of the three.
NestJSToo much DI ceremony for a 1-person backend; learning tax with no payoff at this scale.

References

  • knowledge/tedos-devops-plan.md — DO deployment plan
  • knowledge/tedos-implementation-plan.md — P1 milestones (auth, integrations, AI runs)
  • knowledge/tedos-product-decisions.md — MVP scope
  • decisions/log.md — 2026-05-19 backend = DigitalOcean
  • pg-boss: https://github.com/timgit/pg-boss
  • Drizzle: https://orm.drizzle.team