Architecture Overview¶
Doable is a monorepo of four runnable units (web, api, ws, postgres) plus four shared packages (db, shared, docore, dovault). It's small enough to read end-to-end in a day, but designed so each piece can be replaced independently.
High-level diagram¶
Text-based diagram (for accessibility)
┌────────────────────────────┐
│ End user / browser │
└────────────┬───────────────┘
│ HTTPS / WSS
▼
┌──────────────────┐
│ nginx / Caddy │ TLS termination
└─────┬─────┬──────┘
│ │ /ws
/api,/auth │ ▼
│ ┌───────────┐
│ │ ws │ WebSocket service (Yjs CRDT)
│ └─────┬─────┘
▼ │
┌─────────────┐ │ internal HTTP
│ api │◀──┘
│ (Hono) │
└─────┬───────┘
│
┌─────────────────────┼─────────────────────────────┐
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌────────────────┐
│ Postgres │ │ docore │ │ web (Next) │
│ (pgvector)│ │ AI engine │ │ Server pages │
└───────────┘ └──────┬───────┘ └────────────────┘
│ spawns
▼
┌──────────────────┐
│ Copilot CLI / … │ AI provider subprocess
└──────────────────┘
│ tool calls
▼
┌──────────────────┐
│ dovault (jail) │ cwd + permission sandbox
└────────┬─────────┘
▼
┌──────────────────┐
│ User project FS │ PROJECTS_ROOT/<projectId>/
│ + Vite dev srv │
└──────────────────┘
The four runtime units¶
1. apps/web — Next.js 15 frontend¶
- React 19, Tailwind 4, Monaco editor, Yjs.
- Server Components for the dashboard shell.
- Client Components for the editor, chat, and preview iframe.
- Uses
NEXT_PUBLIC_API_URLandNEXT_PUBLIC_WS_URLto talk to the backend. - Authenticates via JWTs stored in HttpOnly cookies (set by the API).
2. services/api — Hono REST API (Node.js)¶
- Single Node process, ~80 route files mounted in
src/routes.ts. - Owns: auth, projects, files, AI chat, billing, integrations, GitHub sync, deployments, admin, marketplace, analytics.
- Runs migrations, exposes
/health. - Spawns one Vite dev server per active user project (in a sandboxed child process via
dovault). - Acts as a reverse proxy for the live preview at
/preview/:projectId/*.
3. services/ws — WebSocket server¶
- Hono +
wswith Yjs for CRDT documents. - One Y.Doc per project; all collaborators connect to it.
- Awareness protocol carries cursors, selections, and presence.
- Talks back to the API via
INTERNAL_SECRET-signed HTTP for auth and persistence.
4. postgres — PostgreSQL 16¶
- Extensions:
pgcrypto(UUID generation),pgvector(embeddings),pg_trgm(fuzzy search). - Schema lives in
packages/db/migrations/*.sql, applied lexicographically bypnpm db:migrate. - Doable does not use an ORM — queries live in
packages/db/src/queries/*.tsusing thepostgresdriver.
Shared packages¶
| Package | Purpose | Used by |
|---|---|---|
@doable/db |
Connection + typed query helpers | api, ws |
@doable/shared |
Constants, types, KV store abstraction (memory/Redis) | All |
@doable/docore |
The AI agent engine — wraps Copilot SDK, normalizes events, manages tool policies | api |
@doable/dovault |
Runtime jail — Node permissions + OS resource limits | api (when spawning child processes) |
See Monorepo Layout for the complete file tree.
Request lifecycle examples¶
A user message in the chat¶
- Browser →
POST /chat/:projectId/messages(API) with the new prompt. - API authenticates the JWT, loads the project, and starts a streaming response (SSE).
- API delegates to
docore.engine. Docore opens / resumes a session against the configured provider (Copilot CLI, Anthropic, OpenAI). - The provider streams deltas, tool calls, and final messages back through
docore'sEventBus. event-mappernormalizes them; the API forwards normalized events as SSE chunks to the browser.- Tool calls (file edits, shell, fetch) are evaluated against the workspace's policy. Anything filesystem-bound runs inside a
dovaultjail rooted atPROJECTS_ROOT/<projectId>/. - After each file write, the WS service is notified so other collaborators see the new content.
A live preview load¶
- Browser navigates the preview iframe to
/preview/:projectId/index.html. - API checks if a Vite dev server is already running for that project (
projects/dev-server.ts). If not, it spawns one, sandboxed bydovault, on a free localhost port. - API proxies the request to that internal port.
- The API injects the visual edit bridge script into HTML responses so the parent (Doable editor) can pick clicks and update text in place.
Key design decisions¶
- Single API process, no microservices. Easier to deploy, debug, and back up. The WS service exists only because long-lived sockets are awkward to share.
- Filesystem as source of truth for project files. AI agents work best on real files; the DB only stores metadata. The
project_filestable is a cache for fast reads from the dashboard. tsx watchin production for API/WS. Acceptable trade-off: instant updates ongit pull, no separate build step. The web app is built normally for SSR perf.- Bind to
127.0.0.1everywhere; one TLS proxy in front. See Network Binding. - No Redis required. The
kv-storeabstraction defaults to in-memory; flip on Redis only when you scale beyond one API replica.
Where to read next¶
- Monorepo layout — directory-by-directory tour.
- Data model — what the database stores.
- Realtime collaboration — how Yjs is wired up.
- Sandboxing — how user-generated code is contained.