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/websocketfor 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
| Option | Why not |
|---|---|
| Hono + Drizzle + Postgres | Hono is faster on edge but WS plugin is less mature than @fastify/websocket; we are not deploying to edge. |
| Bun + Elysia + Drizzle | Bun 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. |
| NestJS | Too much DI ceremony for a 1-person backend; learning tax with no payoff at this scale. |
References
knowledge/tedos-devops-plan.md— DO deployment planknowledge/tedos-implementation-plan.md— P1 milestones (auth, integrations, AI runs)knowledge/tedos-product-decisions.md— MVP scopedecisions/log.md— 2026-05-19 backend = DigitalOcean- pg-boss: https://github.com/timgit/pg-boss
- Drizzle: https://orm.drizzle.team