The Problem

Teams need a single workspace that merges rich‑text documents, white‑board canvases, and relational tables while keeping data under the user’s control. Existing tools either silo these modalities (Notion, Miro) or rely on opaque cloud services, creating friction for privacy‑first or self‑hosted deployments.

What This Does

AFFiNE delivers an open‑source, local‑first platform that treats every content block—text, sticky notes, embedded pages, code snippets, databases, or slides—as a first‑class element on an edgeless canvas. The repo is a portfolio of four self‑contained projects:

  • packages/ – backend server, common libraries, and the Electron/Android front‑ends (≈4 k code files).
  • blocksuite/ – the core block rendering engine and UI components (≈2.7 k code files).
  • tools/ – CLI utilities and build helpers (≈50 code files).
  • scripts/ – assorted one‑off scripts (≈5 code files).

Key UI pieces live in blocksuite/affine/all/src/blocks/* (e.g., attachment/index.ts, code/index.ts). Backend configuration resides in packages/backend/server/src/base/config/config.ts. The Yarn workspace layout is defined by blocksuite/affine/all/package.json and other package.json files throughout the tree.

How It Is Wired

Execution begins in the backend server. The entry point is the Node process started by the Dockerfile at .github/deployment/node/Dockerfile, which runs the compiled server code that imports packages/backend/server/src/base/config/config.ts. This Config class reads environment variables (e.g., from .docker/dev/.env.example) and wires the Express server, GraphQL endpoint, and WebSocket sync layer.

When a client loads a document, the frontend (Electron, Android, or web) imports block definitions from blocksuite/affine/all/src/blocks/<type>/index.ts. Each block module registers itself with the Blocksuite runtime via a call in blocksuite/affine/all/src/blocks/<type>/index.ts that ultimately invokes the shared factory in blocksuite/affine/shared/src/utils/drag-helper/types.ts. The most traversed utility is drag.ts in blocksuite/affine/fragments/outline/src/utils/drag.ts, defining NoteCardEntity and NoteDropPayload. These types propagate through the drag‑and‑drop pipeline and are consumed by the UI toolbar components (blocksuite/affine/gfx/brush/src/toolbar/components/pen/types.ts defines Pen and PenMap).

On a write operation, the frontend serialises the block graph and sends it over the GraphQL API to the backend. The server stores the payload using the NBStore layer (packages/common/nbstore/src/storage/indexer/field-type.ts defines IndexFieldType). Synchronisation across peers leverages PeerStorageOptions from packages/common/nbstore/src/sync/types.ts. Real‑time updates are broadcast via the WebSocket manager, which ultimately calls getCurrentStore (defined in the same NBStore module) to persist the change.

The only documented external effect is the database connection configured in .docker/selfhost/.env.example. No other filesystem writes are evident beyond the static asset server (packages/frontend/core/public/static/...). The call graph shows a clear hub at the backend Config class and the Blocksuite block registration; there are no circular dependencies reported.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/AFFiNE.git
cd AFFiNE

# Install workspace dependencies
yarn install

# Start a dev environment (Docker Compose builds backend + dev DB)
docker compose -f .devcontainer/docker-compose.yml up --build

# The backend will listen on the port defined in .docker/dev/.env.example
# Front‑end apps can be started via Yarn scripts (e.g., `yarn workspace @affine/web start`)

If you prefer a pure Node build, run:

docker build -t affine-node -f .github/deployment/node/Dockerfile .
docker run -p 3000:3000 affine-node

Configuration files (.env.example in both dev and selfhost) must be copied to .env and populated with valid values before starting.

Real‑World Use

A product team can self‑host AFFiNE behind their VPN, point the DB_URL in .docker/selfhost/.env to an internal PostgreSQL instance, and grant each employee a local desktop client (Electron) that syncs in real time. The UI lets designers drag a prototype sketch onto the same canvas as a requirements doc, while engineers edit code blocks that are version‑controlled via the built‑in Git integration.

Code Health & Issues

  • High – GitHub Actions pinned to mutable tags (.github/workflows/*). Replace @vN with a 40‑char SHA.
  • High – Plaintext .env committed in blocksuite/playground/.env. Remove, add to .gitignore, rotate secrets.
  • Medium – Dockerfile uses mutable node:22-bookworm-slim base image. Pin by digest.
  • Medium – No dependency‑vulnerability scan in CI. Add dependency-review-action or osv-scanner.
  • Medium – No pre‑commit secret scan. Install a secret‑checking hook.
  • Medium – Large binaries (githubStar.mp4, GIF) stored in Git. Move to LFS or external storage.
  • Medium – Checkout step retains token; set persist-credentials: false in release workflow.
  • Medium – Container runs as root; add a non‑root USER.
  • Low – Workflow jobs lack timeout-minutes; add reasonable limits.

The repository includes a full test suite (tests/), CI via GitHub Actions, a Dockerfile, and a LICENSE, but the presence of committed secrets and mutable CI references reduces its immediate production readiness.

The Bottom Line

AFFiNE provides a modular, privacy‑first canvas that unifies document, whiteboard, and database workflows, with a clear separation between core block engine (blocksuite/) and multiple front‑end targets (packages/). The codebase is sizable and well‑structured, but high‑severity CI and secret‑management issues must be resolved before a secure self‑hosted deployment. Ideal for teams willing to invest in fixing the CI pipeline and secret handling to gain a flexible, open‑source alternative to proprietary knowledge‑base tools.