# AFFiNE Backend Architecture: How the TypeScript and Rust Codebase is Structured

> Explore the AFFiNE backend architecture, a NestJS TypeScript API server powered by a Rust storage engine via NAPI-RS for efficient local-first collaborative editing.

- Repository: [Toeverything/AFFiNE](https://github.com/toeverything/AFFiNE)
- Tags: architecture
- Published: 2026-03-05

---

**The AFFiNE backend combines a NestJS TypeScript API server with a high-performance Rust storage engine, using NAPI-RS bindings to bridge the two for local-first collaborative editing.**

The open-source AFFiNE repository (`toeverything/AFFiNE`) implements a modular backend architecture that splits responsibilities between a Node.js application server and a native Rust binary. This structure enables high-performance CRDT operations while maintaining a flexible TypeScript API layer for HTTP and GraphQL endpoints.

## Overview of the AFFiNE Backend Stack

According to the toeverything/AFFiNE source code, the backend is organized as two primary Yarn workspaces defined in the root [`tsconfig.json`](https://github.com/toeverything/AFFiNE/blob/main/tsconfig.json):

- **`packages/backend/server`**: The TypeScript application server built on **NestJS**
- **`packages/backend/server-native`**: The Rust storage module compiled via **Cargo** and exposed to Node via **napi-rs**

These workspaces share a unified build system. The root [`tsconfig.json`](https://github.com/toeverything/AFFiNE/blob/main/tsconfig.json) references both paths to enable cross-package development:

```json
{
  "references": [
    { "path": "./packages/backend/server" },
    { "path": "./packages/backend/server-native" }
  ]
}

```

**Source:** [[`tsconfig.json`](https://github.com/toeverything/AFFiNE/blob/main/tsconfig.json)](https://github.com/toeverything/AFFiNE/blob/canary/tsconfig.json)

## The TypeScript Layer: @affine/server

The `@affine/server` workspace implements the public API surface, authentication, job scheduling, and real-time communication layers.

### NestJS Framework and Core Infrastructure

The server bootstraps from [`packages/backend/server/src/main.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/backend/server/src/main.ts) and defines its module structure in [`packages/backend/server/src/app.module.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/backend/server/src/app.module.ts). This module imports **GraphQL**, **WebSocket** gateways, and **BullMQ** queues required for asynchronous processing.

Key technologies include:

- **NestJS** (core, platform-express, websockets) for the HTTP server and dependency injection
- **Apollo Server** for GraphQL query handling
- **TRPC** and **Hono** for additional API routing options
- **Swagger** for API documentation

### Background Job Processing with BullMQ

Long-running tasks execute outside the request cycle through **BullMQ** processors. For example, email notifications run via a dedicated job processor:

```typescript
// packages/backend/server/src/jobs/email.job.ts
import { Processor, Process } from '@nestjs/bull';
import { Job } from 'bullmq';

@Processor('email')
export class EmailProcessor {
  @Process()
  async handle(job: Job<{ to: string; subject: string; body: string }>) {
    // call external mail service
    await sendMail(job.data);
  }
}

```

**Source:** [[`email.job.ts`](https://github.com/toeverything/AFFiNE/blob/main/email.job.ts)](https://github.com/toeverything/AFFiNE/blob/canary/packages/backend/server/src/jobs/email.job.ts)

## The Rust Layer: @affine/server-native

The `@affine/server-native` workspace provides the persistence layer through a Rust binary. It handles CRDT state management and local-first storage via **OctoBase** and **y-octo**.

### OctoBase Storage and y-octo CRDTs

OctoBase is an embedded, multi-tenant storage engine that uses SQLite under the hood. It integrates **y-octo** (a native Rust implementation of Yjs CRDTs) to manage document state and synchronization. The Rust crate is defined in the root [`Cargo.toml`](https://github.com/toeverything/AFFiNE/blob/main/Cargo.toml):

```toml
[package]
name = "affine"
version = "0.12.0"
edition = "2021"

[dependencies]
anyhow = "1"
clap = { version = "4.4", features = ["derive"] }
env_logger = "0.11"
log = "0.4"
octobase = { version = "0.6", features = ["fs"] }
y-octo = "0.2"

```

**Source:** [[`Cargo.toml`](https://github.com/toeverything/AFFiNE/blob/main/Cargo.toml)](https://github.com/toeverything/AFFiNE/blob/canary/Cargo.toml)

### NAPI-RS Bindings

The Rust functions are exposed to the TypeScript runtime through **NAPI-RS** bindings. The TypeScript side of this bridge in [`packages/backend/server-native/src/index.ts`](https://github.com/toeverything/AFFiNE/blob/main/packages/backend/server-native/src/index.ts) exports functions that internally call the compiled binary:

```typescript
// packages/backend/server-native/src/index.ts
import { OctoBase } from 'octobase';
import { Yocto } from 'y-octo';

export async function initDB(path: string) {
  const db = await OctoBase.open(path);
  const crdt = new Yocto(db);
  return { db, crdt };
}

export async function ping() {
  // lightweight health check implemented in Rust
  return 'online';
}

```

**Source:** [[`index.ts`](https://github.com/toeverything/AFFiNE/blob/main/index.ts)](https://github.com/toeverything/AFFiNE/blob/canary/packages/backend/server-native/src/index.ts)

## Integration: Bridging TypeScript and Rust

The TypeScript server invokes native operations through the `@affine/server-native` package. For example, a health check controller calls the Rust `ping` implementation directly:

```typescript
// packages/backend/server/src/app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ServerNative } from '@affine/server-native';

@Controller('health')
export class HealthController {
  @Get()
  async check() {
    const status = await ServerNative.ping(); // NAPI call to Rust side
    return { ok: true, storage: status };
  }
}

```

**Source:** [[`app.controller.ts`](https://github.com/toeverything/AFFiNE/blob/main/app.controller.ts)](https://github.com/toeverything/AFFiNE/blob/canary/packages/backend/server/src/app.controller.ts)

### Development Workflow

The CLI script at [`tools/cli/src/run.ts`](https://github.com/toeverything/AFFiNE/blob/main/tools/cli/src/run.ts) detects the target workspace and loads the appropriate runtime. Use the following commands during development:

- **Start the TypeScript server**: `yarn affine dev -p @affine/server`
- **Build the native module**: `yarn workspace @affine/server-native run build` (invokes `cargo build`)

**Source:** [[`run.ts`](https://github.com/toeverything/AFFiNE/blob/main/run.ts)](https://github.com/toeverything/AFFiNE/blob/canary/tools/cli/src/run.ts)

## Summary

- **The AFFiNE backend uses a hybrid TypeScript/Rust architecture** where Node.js handles the API and Rust manages storage.
- **`@affine/server`** provides the NestJS HTTP server, GraphQL endpoints, WebSocket real-time sync, and BullMQ job processing.
- **`@affine/server-native`** delivers high-performance CRDT operations via OctoBase and y-octo, compiled as a Rust binary with NAPI-RS bindings.
- **Configuration files** [`tsconfig.json`](https://github.com/toeverything/AFFiNE/blob/main/tsconfig.json) and [`Cargo.toml`](https://github.com/toeverything/AFFiNE/blob/main/Cargo.toml) orchestrate the multi-language workspace structure.
- **Integration** happens through NAPI-RS calls, allowing TypeScript controllers to execute Rust-native storage operations seamlessly.

## Frequently Asked Questions

### What is the primary framework used for the AFFiNE backend API?

The primary framework is **NestJS**, which provides the HTTP server, dependency injection, GraphQL integration via Apollo, and WebSocket support. It is implemented in the `packages/backend/server` workspace.

### How does AFFiNE handle data persistence and synchronization?

Data persistence is handled by the **OctoBase** storage engine written in Rust, which uses SQLite for local storage and **y-octo** for CRDT-based synchronization. This enables local-first editing where changes sync across clients in real time.

### What is the role of Rust in the AFFiNE backend architecture?

Rust provides the **native storage module** (`@affine/server-native`) that compiles to a binary and exposes high-performance database and CRDT functions to Node.js via **NAPI-RS** bindings. This avoids JavaScript's performance limitations for intensive data operations.

### How are background jobs managed in AFFiNE?

Background jobs use **BullMQ**, a Redis-backed queue system for Node.js. Processes like email sending run in separate workers defined in the `jobs` directory of the server package, while the main NestJS application enqueues tasks via the `@nestjs/bull` decorators.