AFFiNE
Audited from github.com/toeverything/AFFiNE
AFFiNE is an open-source, local-first knowledge workspace that merges documents, whiteboards and databases into one block-based canvas, available as web, desktop (Electron) and mobile apps with optional cloud sync.
- Language
- TypeScript
- Database
- PostgreSQL
- Hosting
- Docker
- Running for
- 2 years 6 months
- Team
- 6-20 people
Why this architecture
A single Yjs CRDT model lets docs, canvas and tables share one editor and sync engine; local-first storage in SQLite/IndexedDB keeps the app usable offline while a NestJS GraphQL server adds collaboration and cloud features.
Tech stack by layer
15 technologies · audited Sep 17, 2026- ReactApplication shell in `packages/frontend/core`, routed with `react-router-dom`; state with Jotai and RxJS via `@toeverything/infra`.
- TypeScriptAll web, desktop and mobile packages; `typecheck` runs `tsc -b` over the whole workspace.
- BlockSuiteIn-repo block editor framework (`blocksuite/`) that renders docs, whiteboard and database blocks on one canvas; built on Lit web components.
- YjsCRDT document model shared by editor, sync layer and server (`yjs`, `y-protocols`).
- vanilla-extractZero-runtime CSS for components alongside Emotion and the `@toeverything/theme` tokens.
- ElectronDesktop app in `packages/frontend/apps/electron` packaged with Electron Forge (dmg, deb, squirrel, flatpak, AppImage makers) and `electron-updater`.
- CapacitoriOS and Android apps wrap the web core with `@capacitor/ios` and `@capacitor/android`.
- NestJSServer in `packages/backend/server` on Express with Nest modules for GraphQL, websockets, scheduling and throttling.
- GraphQLPrimary API via `@nestjs/graphql` and Apollo Server; a shared `@affine/graphql` package holds the client documents.
- Socket.IORealtime doc sync over `@nestjs/platform-socket.io` with the Redis adapter for multi-instance deployments.
- RustNative modules via napi-rs for the server and desktop (`packages/*/native`), plus `uniffi` bindings for the mobile apps.
- OpenTelemetryTracing and metrics instrumentation for HTTP, GraphQL, Nest, ioredis and Socket.IO with Prometheus and Zipkin exporters.
- PostgreSQLServer database via Prisma; `PLpgSQL` in the language stats comes from the migration SQL. Render config provisions a managed Postgres.
- PrismaORM and migrations for the server (`prisma`, `@prisma/client`, `data-migration` script).
- RedisCache, pub/sub and the Socket.IO adapter (`ioredis`); the Rust workspace also depends on the `redis` crate.
- SQLiteLocal-first storage on desktop and mobile through the Rust `nbstore`/`sqlite_v1` crates using `sqlx`; browsers use IndexedDB (`idb`).
- S3Blob storage behind an S3-compatible adapter in `packages/common/s3-compat`.
- DockerOfficial images built by `build-images.yml`; self-host compose files live in `.docker/selfhost`.
- KubernetesHelm chart and deployment manifests under `.github/helm` and `.github/deployment` for the cloud release.
- Render`render.yaml` deploys the Docker image as a web service with a Postgres database, a key-value store and a persistent disk.
- SentryError reporting in web (`@sentry/react`), desktop (`@sentry/electron`) and the tracking package.
- VitestUnit tests for frontend packages; the server uses `ava` with `supertest` and `c8` coverage.
- PlaywrightEnd-to-end suites under `tests/` for local, cloud, desktop and mobile targets.
- oxlintLinting and formatting with `oxlint`/`oxfmt`, `taplo` for TOML and `cargo fmt` for Rust through lint-staged.
- GitHub ActionsBuild/test, image builds, and separate release pipelines for cloud, desktop and mobile.
AFFiNE architecture diagram
Open SVGDiagram as text
- Web app (React · BlockSuite) → Server (NestJS · GraphQL): GraphQL
- Web app (React · BlockSuite) → Doc sync (Socket.IO): Yjs updates
- Desktop & mobile (Electron · Capacitor) → Local store (SQLite (nbstore)): local-first
- Desktop & mobile (Electron · Capacitor) → Doc sync (Socket.IO): Yjs updates
- Server (NestJS · GraphQL) → PostgreSQL (Prisma): Prisma
- Doc sync (Socket.IO) → Redis (cache · pub/sub): adapter
- Server (NestJS · GraphQL) → Blob storage (S3-compatible): blobs
- Server (NestJS · GraphQL) → Native modules (Rust · napi-rs): hot paths
Key architectural decisions
6 decisions- 01
One CRDT document model for docs, whiteboard and tables
Everything is a Yjs document edited through the in-repo BlockSuite framework (
blocksuite/), so a page can mix text blocks, an edgeless canvas and database views without separate data models.yjsappears in the server, the core app and even the Electron main process. - 02
Local-first storage, with the server as a sync peer
Desktop and mobile persist documents in SQLite through the Rust
nbstorecrate (packages/frontend/native/nbstore), browsers use IndexedDB, and the NestJS server syncs updates over Socket.IO and stores them in PostgreSQL. The app stays usable offline and the server is optional for a single user. - 03
Rust for the hot paths, exposed through napi-rs and uniffi
The Cargo workspace builds native modules for the server (
packages/backend/native), the desktop app (packages/frontend/native) and mobile (packages/frontend/mobile-native).napibridges Node/Electron,uniffigenerates Swift and Kotlin bindings, which is why those languages show up in the repository. - 04
A single web core reused by web, Electron and Capacitor shells
packages/frontend/coreholds the product;apps/web,apps/electron-renderer,apps/ios,apps/androidandapps/mobileare thin shells that add platform APIs (@affine/electron-api,@capacitor/*). - 05
GraphQL-first server with heavy observability
The NestJS server exposes Apollo GraphQL plus Socket.IO gateways and ships OpenTelemetry instrumentation for HTTP, GraphQL, Redis and sockets with Prometheus and Zipkin exporters, reflecting that the same code powers the hosted AFFiNE Cloud.
- 06
Dual licensing signals in the repository
GitHub reports the license as "Other" while the root
package.jsondeclares MIT and bothLICENSEandLICENSE-MITfiles exist. Check both files before reusing code.
How AFFiNE is built
How AFFiNE is structured
AFFiNE is a Yarn workspace on Node 22 (.yarnrc.yml, engines.node >=22.12) with a Cargo workspace alongside it. The root package.json lists the workspace groups: blocksuite/**/* (the editor framework), packages/*/* (backend, common and frontend libraries), packages/frontend/apps/* (the shells), tools/* and tests/*. A custom CLI (yarn affine …, from tools/cli) drives dev, build and init.
blocksuite/frameworkandblocksuite/affineimplement the block editor: docs, the edgeless whiteboard and database views are all blocks on one canvas.packages/frontend/coreis the product UI;packages/frontend/componentis the design system with Storybook;i18n,routes,trackandtemplatesare supporting libraries.packages/frontend/apps/*are shells:web,electron+electron-renderer,ios,android,mobileandmobile-shared.packages/backend/serveris the NestJS server;packages/backend/nativeits Rust companion.packages/common/*holds shared code such asauth,graphql,infra,nbstore,realtime,readerands3-compat.Cargo.tomldeclares the Rust members:backend/native,common/native,frontend/native(withnbstore,schema,sqlite_v1) andfrontend/mobile-native.
Frontend
The UI is React with react-router-dom, Jotai atoms and RxJS-based services from @toeverything/infra. Styling mixes vanilla-extract, Emotion and the @toeverything/theme tokens; Radix UI primitives, cmdk and sonner provide the interaction layer. The editor itself is BlockSuite, built on Lit web components and wired to Yjs documents (yjs, y-protocols). Rendering extras include mermaid, katex, shiki and a Typst renderer, and @toeverything/pdf-viewer handles attachments. The browser build stores data in IndexedDB (idb) and talks to the server with GraphQL and socket.io-client.
Desktop is Electron packaged by Electron Forge with makers for macOS, Windows, Debian, Flatpak and AppImage, auto-updates via electron-updater, and native capabilities from @affine/native. Mobile apps are Capacitor shells (@capacitor/ios, @capacitor/android) around the same core, with Rust bindings generated by uniffi.
Backend & APIs
packages/backend/server is a NestJS application on Express. The API is GraphQL through @nestjs/graphql and Apollo Server with graphql-upload for files; websockets run through @nestjs/platform-socket.io with @socket.io/redis-adapter. Cross-cutting concerns use @nestjs/throttler, @nestjs/schedule, nestjs-cls (with a Prisma transactional adapter), winston logging and piscina worker threads. Auth uses jose/jsonwebtoken with @node-rs/argon2 hashing; email is rendered with react-email and sent via nodemailer. The server ships a full OpenTelemetry setup (HTTP, GraphQL, Nest, ioredis and Socket.IO instrumentation; Prometheus, Zipkin and Google Cloud Trace exporters), and exa-js plus tiktoken in the native package support the Copilot features.
Data & persistence
Server data lives in PostgreSQL through Prisma (prisma, @prisma/client, a data-migration script); the migration SQL is why PLpgSQL appears in the language stats. Redis (ioredis) provides caching and the Socket.IO adapter. On desktop and mobile, documents persist locally in SQLite through the Rust nbstore and sqlite_v1 crates using sqlx; in the browser, IndexedDB. Blobs go to S3-compatible storage through packages/common/s3-compat.
Realtime / collaboration
Every document is a Yjs CRDT, so edits from multiple clients merge deterministically. Clients exchange updates with the server over Socket.IO; the Redis adapter lets several server instances share rooms. The packages/common/realtime package and the Rust y-octo crate (a Yjs implementation in Rust) support the sync layer on both sides.
Build, test & deploy
- Linting is
oxlintandoxfmt(withtaplofor TOML andcargo fmtfor Rust) through husky and lint-staged;typecheckrunstsc -b. - Tests: Vitest for frontend packages,
ava+supertestfor the server, Playwright suites intests/affine-local,affine-cloud,affine-desktopandaffine-mobile; coverage goes to Codecov (codecov.yml). - CI:
build-test.yml,build-images.yml,copilot-test.yml; releases are split intorelease.yml,release-cloud.yml,release-desktop.ymlandrelease-mobile.yml, withscripts/set-version.shandtools/changelogsupporting the process.
Self-hosting notes
Official images are built by build-images.yml, and .docker/selfhost contains the compose setup for a self-hosted server. render.yaml is a ready blueprint: it runs the Docker image as a web service with DATABASE_URL from a managed Postgres, REDIS_SERVER_* from a key-value service, a /info health check and a persistent disk mounted at /root/.affine for uploaded blobs and the generated private key. A Helm chart under .github/helm targets Kubernetes. Because clients are local-first, the server only needs to be reachable when you want sync, sharing or cloud features.
What to copy (and what not to)
Copy the "one CRDT document, many views" idea if your product blends docs and canvases; it removes a whole class of data-sync bugs. Copy the thin-shell pattern for Electron and Capacitor around a single web core. Be careful copying the Rust layer: it pays off here because storage, media and mobile bindings are shared across four platforms, but it doubles the toolchain (Cargo, napi-rs, uniffi) for a team that only ships a web app.
Sources & repo audit
- Audited
- Sep 17, 2026
- Commit
- 2af3077
- License
- NOASSERTION
- Root package.json (yarn workspaces incl. blocksuite/**, oxlint, vitest)
- packages/backend/server/package.json (NestJS, Apollo, Prisma, socket.io, yjs, OpenTelemetry)
- packages/frontend/core/package.json (React, BlockSuite, yjs, jotai, socket.io-client, idb)
- Cargo.toml (napi, uniffi, sqlx sqlite, y-octo, redis)
- packages/frontend/apps/electron/package.json (Electron Forge makers, electron-updater)
- render.yaml (Docker web service, Postgres, key-value store, persistent disk)
Independent analysis of repository at github.com/toeverything/AFFiNE. Spotted an inaccuracy? Use the claim form to request a correction.
Show this stack on your README or website
[](https://stackitfast.com/project/affine)Paste it into your README. It shows your current stack and links to this page.
Scaffold it with your agent
Paste this prompt into Claude Code, Cursor, Windsurf or AGY to start a project with AFFiNE's architecture.
- 1Copy the promptThe full markdown spec, with every layer and decision.
- 2Open your AI toolClaude Code, Cursor, Windsurf or Copilot, in a new repo.
- 3Paste and scaffoldUse it as the first instruction; review before you ship.
# MISSION: Scaffold "AFFiNE" Production Architecture
You are an expert Senior Staff Software Architect and Full-Stack Engineer. Your mission is to scaffold and implement a production-grade, highly reliable, and modular codebase following the proven architecture of **AFFiNE**.
---
## 1. PROJECT SPECIFICATIONS & BENCHMARK
- **Reference Architecture**: AFFiNE
- **What It Does**: AFFiNE is an open-source, local-first knowledge workspace that merges documents, whiteboards and databases into one block-based canvas, available as web, desktop (Electron) and mobile apps with optional cloud sync.
- **Domain & Category**: Collaborative Workspace
- **Production Scale**: 6-20 people
- **Development Mode**: HYBRID
- **Architectural Rationale**: A single Yjs CRDT model lets docs, canvas and tables share one editor and sync engine; local-first storage in SQLite/IndexedDB keeps the app usable offline while a NestJS GraphQL server adds collaboration and cloud features.
- **Live Website Reference**: https://affine.pro
- **Source Repository**: https://github.com/toeverything/AFFiNE
---
## 2. PRODUCTION TECH STACK
- **Full Stack Array**: TypeScript, Rust, React, Yjs, NestJS, GraphQL, Prisma, PostgreSQL, Redis, Socket.IO, SQLite, Electron, Capacitor, Docker, Kubernetes
- **Primary Language(s)**: TypeScript, Rust
- **License of the reference repo**: NOASSERTION
- **Frontend**: React — Application shell in `packages/frontend/core`, routed with `react-router-dom`; state with Jotai and RxJS via `@toeverything/infra`.; TypeScript — All web, desktop and mobile packages; `typecheck` runs `tsc -b` over the whole workspace.; BlockSuite — In-repo block editor framework (`blocksuite/`) that renders docs, whiteboard and database blocks on one canvas; built on Lit web components.; Yjs — CRDT document model shared by editor, sync layer and server (`yjs`, `y-protocols`).; vanilla-extract — Zero-runtime CSS for components alongside Emotion and the `@toeverything/theme` tokens.; Electron — Desktop app in `packages/frontend/apps/electron` packaged with Electron Forge (dmg, deb, squirrel, flatpak, AppImage makers) and `electron-updater`.; Capacitor — iOS and Android apps wrap the web core with `@capacitor/ios` and `@capacitor/android`.
- **Backend & APIs**: NestJS — Server in `packages/backend/server` on Express with Nest modules for GraphQL, websockets, scheduling and throttling.; GraphQL — Primary API via `@nestjs/graphql` and Apollo Server; a shared `@affine/graphql` package holds the client documents.; Socket.IO — Realtime doc sync over `@nestjs/platform-socket.io` with the Redis adapter for multi-instance deployments.; Rust — Native modules via napi-rs for the server and desktop (`packages/*/native`), plus `uniffi` bindings for the mobile apps.; OpenTelemetry — Tracing and metrics instrumentation for HTTP, GraphQL, Nest, ioredis and Socket.IO with Prometheus and Zipkin exporters.
- **Data & persistence**: PostgreSQL — Server database via Prisma; `PLpgSQL` in the language stats comes from the migration SQL. Render config provisions a managed Postgres.; Prisma — ORM and migrations for the server (`prisma`, `@prisma/client`, `data-migration` script).; Redis — Cache, pub/sub and the Socket.IO adapter (`ioredis`); the Rust workspace also depends on the `redis` crate.; SQLite — Local-first storage on desktop and mobile through the Rust `nbstore`/`sqlite_v1` crates using `sqlx`; browsers use IndexedDB (`idb`).; S3 — Blob storage behind an S3-compatible adapter in `packages/common/s3-compat`.
- **Infrastructure & deploy**: Docker — Official images built by `build-images.yml`; self-host compose files live in `.docker/selfhost`.; Kubernetes — Helm chart and deployment manifests under `.github/helm` and `.github/deployment` for the cloud release.; Render — `render.yaml` deploys the Docker image as a web service with a Postgres database, a key-value store and a persistent disk.; Sentry — Error reporting in web (`@sentry/react`), desktop (`@sentry/electron`) and the tracking package.
- **Tooling, testing & ops**: Vitest — Unit tests for frontend packages; the server uses `ava` with `supertest` and `c8` coverage.; Playwright — End-to-end suites under `tests/` for local, cloud, desktop and mobile targets.; oxlint — Linting and formatting with `oxlint`/`oxfmt`, `taplo` for TOML and `cargo fmt` for Rust through lint-staged.; GitHub Actions — Build/test, image builds, and separate release pipelines for cloud, desktop and mobile.
---
## 3. KEY ARCHITECTURAL DECISIONS (audited from https://github.com/toeverything/AFFiNE @ 2af3077)
1. **One CRDT document model for docs, whiteboard and tables**: Everything is a Yjs document edited through the in-repo BlockSuite framework (`blocksuite/`), so a page can mix text blocks, an edgeless canvas and database views without separate data models. `yjs` appears in the server, the core app and even the Electron main process.
2. **Local-first storage, with the server as a sync peer**: Desktop and mobile persist documents in SQLite through the Rust `nbstore` crate (`packages/frontend/native/nbstore`), browsers use IndexedDB, and the NestJS server syncs updates over Socket.IO and stores them in PostgreSQL. The app stays usable offline and the server is optional for a single user.
3. **Rust for the hot paths, exposed through napi-rs and uniffi**: The Cargo workspace builds native modules for the server (`packages/backend/native`), the desktop app (`packages/frontend/native`) and mobile (`packages/frontend/mobile-native`). `napi` bridges Node/Electron, `uniffi` generates Swift and Kotlin bindings, which is why those languages show up in the repository.
4. **A single web core reused by web, Electron and Capacitor shells**: `packages/frontend/core` holds the product; `apps/web`, `apps/electron-renderer`, `apps/ios`, `apps/android` and `apps/mobile` are thin shells that add platform APIs (`@affine/electron-api`, `@capacitor/*`).
5. **GraphQL-first server with heavy observability**: The NestJS server exposes Apollo GraphQL plus Socket.IO gateways and ships OpenTelemetry instrumentation for HTTP, GraphQL, Redis and sockets with Prometheus and Zipkin exporters, reflecting that the same code powers the hosted AFFiNE Cloud.
6. **Dual licensing signals in the repository**: GitHub reports the license as "Other" while the root `package.json` declares MIT and both `LICENSE` and `LICENSE-MIT` files exist. Check both files before reusing code.
---
## 4. NON-NEGOTIABLE ARCHITECTURAL GUARDRAILS
1. **Monorepo & Modular Separation**:
- Structure as a Turborepo monorepo with strict package boundaries:
- `apps/web`: Application UI, routing, layouts, and server endpoints.
- `packages/ui`: Shared design tokens, CSS variables, and Radix UI primitive components.
- `packages/db`: Database schemas, client singleton, declarative migrations, and seed scripts.
- `packages/config`: Shared TypeScript, ESLint, and build configurations.
2. **Strict Type Safety & Zero `any` Policy**:
- Enable `strict: true`, `noImplicitAny: true`, and `strictNullChecks: true`.
- Validate ALL external inputs, API request bodies, and query parameters with **Zod** schemas before execution.
3. **Frontend & Rendering Guidelines**:
- Isolate interactive UI state to leaf components. Keep core pages lightweight and performant.
4. **Design System & Aesthetics**:
- Keep every color, radius, shadow and font in a single token file (CSS variables) and consume tokens everywhere; never hardcode hex values in components.
- Prefer crisp 1px borders and one subtle shadow scale over blurry default shadows. Pair one sans-serif for body/headings with one monospace for tags, badges, metrics, and code.
5. **Data Layer & Reliability**:
- Write declarative schema definitions with foreign keys, composite indexes on queried filters, and automated timestamp triggers.
- Use connection pooling and prepared statements for serverless database execution.
---
## 5. STEP-BY-STEP SCAFFOLDING ROADMAP
- **Phase 1: Workspace & Root Config**: Initialize package manager, monorepo configuration (`turbo.json`, `tsconfig.base.json`, `package.json`).
- **Phase 2: Database Schema & Client**: Set up the data layer (PostgreSQL, Prisma, Redis, SQLite, S3): client, connection pool, models, and migration scripts.
- **Phase 3: Design Tokens & UI Primitives**: Build accessible `Button`, `Input`, `Card`, `Badge`, and layout wrappers inside `packages/ui`.
- **Phase 4: Core Application Routes & Handlers**: Implement primary authentication, user session handling, and application routes.
- **Phase 5: Quality Assurance & Build Verification**: Run `tsc --noEmit`, ESLint, Prettier, and smoke test suites to ensure zero compilation or runtime errors.
---
## 6. EXECUTION INSTRUCTIONS
1. Review all specifications, architectural guardrails, and stack choices above.
2. Present the full monorepo directory tree structure.
3. Systematically generate the complete, production-ready codebase according to the 5-phase roadmap above — starting with the root workspace setup, followed by the database schema, UI design system package, and full-stack application routes until the repository is fully scaffolded and ready to run.Frequently asked about AFFiNE
What is AFFiNE built with?
AFFiNE is a TypeScript and Rust monorepo. The web app is React on top of the in-repo BlockSuite editor framework with Yjs CRDT documents; the server is NestJS with Apollo GraphQL, Prisma on PostgreSQL, Redis and Socket.IO; desktop uses Electron and mobile uses Capacitor, with Rust native modules bridged through napi-rs and uniffi.
Does AFFiNE work offline or without a server?
Yes. It is local-first: documents are stored in SQLite on desktop and mobile through the Rust nbstore crate and in IndexedDB in the browser. The server acts as a sync and collaboration peer and is optional for single-user use.
How does AFFiNE sync and collaborate in real time?
Documents are Yjs CRDTs, so concurrent edits merge without conflicts. Updates travel over Socket.IO to the NestJS server, which uses the Redis adapter to fan out between instances and persists updates in PostgreSQL.
Can I self-host AFFiNE?
Yes. The repository ships official Docker images, self-host compose files under .docker/selfhost, a Helm chart under .github/helm, and a render.yaml blueprint that provisions the web service, PostgreSQL and a Redis-compatible store on Render.
Why does AFFiNE use Rust?
Performance-sensitive and platform-specific parts live in a Cargo workspace: local SQLite storage, document loading and import, audio and media handling, and a Rust Yjs implementation (y-octo). napi-rs exposes them to Node and Electron; uniffi generates Swift and Kotlin bindings for the mobile apps.
One email a month: new deep dives and stack trends
New source-audited architectures, head-to-head comparisons and the monthly stack report. No spam, unsubscribe anytime.
Similar architectures
- HoppscotchClassicAPI Testing Client · 6-20 peopleShares TypeScript · NestJS · GraphQL
- NocoDBHybridSmart Database Spreadsheet · 6-20 peopleShares TypeScript · NestJS · PostgreSQL
- TwentyHybridCRM SaaS · 6-20 peopleShares TypeScript · React · NestJS
- HasuraClassicInstant GraphQL Engine & Unified Data Access · 20+ peopleShares TypeScript · Rust · GraphQL
- LemmyClassicFederated Link Aggregator & Forum · 20+ peopleShares Rust · PostgreSQL · Docker
- Cal.comClassicScheduling SaaS · 6-20 peopleShares TypeScript · React · PostgreSQL