Browse docs

Architecture

System overview, components, and technology stack

This page describes Alex's runtime architecture for contributors and operators.

System overview

Alex has five major runtime pieces:

  1. Next.js application - Serves the UI and API routes.
  2. Rust runtime (watcher-rs) - Handles ingestion, metadata extraction, cover generation, database bridge commands, and tunnel client.
  3. SQLite database - Stores users, books, progress, collections, and settings.
  4. Storage backend(s) - Local folder or S3-compatible bucket, selected by configuration.
  5. Relay service (optional) - Reverse tunnel (alex-relay) for public access without port forwarding.

Architecture diagram

Rendering diagram...

Core components

Next.js application

The Next.js app is the control plane for UI and HTTP APIs.

Key routes include:

  • /library - Main grid with search, filters, sort
  • /read/[bookId] - Reader route (PDF or EPUB)
  • /collections and /collections/[id] - Collection management
  • /shared/[token] - Public collection view
  • /admin/* - User and library administration
  • /setup and /onboarding - First-run setup flows

Authentication (dual system)

Alex uses two authentication mechanisms that can coexist on the same server:

  • Web auth (NextAuth.js v5) - Credential-based email/password login with JWT sessions (30-day expiry). Two NextAuth instances share the same secret and pinned cookie configuration:
    • Full config - Used by API route handlers; includes credential provider and DB queries
    • Middleware config - Lightweight Edge-runtime instance; JWT decode only, no providers
  • Desktop auth - When ALEX_DESKTOP=true, Electron generates a per-session random token (ALEX_DESKTOP_AUTH_TOKEN) and injects it via the x-alex-desktop-auth header on all requests. Middleware validates this header and grants a synthetic admin session.

Cookie names are pinned (with __Secure- prefix when NEXTAUTH_URL is HTTPS) to prevent mismatches behind reverse proxies. Middleware respects X-Forwarded-Host and X-Forwarded-Proto headers for correct redirects behind the relay.

Source-aware file serving

All book-byte endpoints use the same source pipeline:

  • /api/books/[id]/file
  • /api/books/[id]/book.epub
  • /api/shared/[token]/books/[bookId]/file
  • /api/shared/[token]/books/[bookId]/book.epub

The server reads books.source and dispatches to the correct driver:

  • local - stream from disk with range support
  • s3 - stream from watcher-rs s3-stream

Rust runtime (watcher-rs)

watcher-rs runs independently from the Next.js server and has four responsibilities:

  1. Ingestion engine
  • Local mode: recursive file events via notify
  • S3 mode: poll + diff object keys
  • Handlers for add/change/delete update the DB and covers
  1. Metadata and cover generation
  • PDF metadata via lopdf
  • EPUB metadata via zip + quick-xml
  • Covers via pdfium-render, EPUB extraction, or fallback generation
  1. Bridge commands for Next.js
  • watcher-rs db for SQL execution (query-all, query-one, execute)
  • watcher-rs s3-stream for object streaming
  1. Tunnel client
  • Connects to the relay service via binary WebSocket (tokio-tungstenite)
  • Forwards HTTP requests from the relay to the local Next.js server
  • Uses bincode for efficient binary serialization of request/response frames

Alex Relay (alex-relay)

The relay is an optional standalone Rust service that enables public access to a desktop Alex instance without port forwarding or dynamic DNS.

  • Built on Axum with WebSocket upgrade for tunnel connections
  • Uses binary WebSocket frames carrying serialized HTTP request/response pairs (bincode + serde)
  • Incoming requests to relay.alexreader.app are proxied through the tunnel to the desktop app's local server
  • Preserves Set-Cookie and other response headers; injects X-Forwarded-Host and X-Forwarded-Proto
  • Key modules: relay.rs (connection management), proxy.rs (request proxying), protocol.rs (frame types)

SQLite database

The DB is a single file (typically library.db) with tables for:

  • users
  • books
  • reading_progress
  • collections
  • collection_books
  • settings (library_version drives SSE updates)

Data flows

Book ingestion

  1. Runtime selects local or S3 mode.
  2. watcher-rs detects adds/changes/deletes.
  3. It computes hashes, extracts metadata, and generates covers.
  4. It writes books rows (including source metadata for S3 records).
  5. It increments settings.library_version.
  6. /api/library/events pushes update notifications.

Reading

  1. User opens a book.
  2. API returns metadata + saved progress.
  3. Reader requests file bytes.
  4. Source driver streams from local disk or S3.
  5. Progress updates are persisted as the user reads.

Public sharing

  1. Owner enables sharing for a collection.
  2. A tokenized URL is generated.
  3. Public endpoints validate collection membership per request.
  4. Public readers store progress in browser local storage (not server-side).

Relay tunnel (desktop only)

  1. Electron spawns the tunnel daemon alongside the watcher and Next.js server.
  2. Tunnel client connects to wss://relay.alexreader.app/_tunnel/ws.
  3. Incoming HTTP requests are serialized as bincode frames and forwarded through the WebSocket.
  4. Tunnel client deserializes requests and proxies them to localhost:3210.
  5. Responses (including Set-Cookie headers) are forwarded back through the tunnel.
  6. Relay injects forwarded headers so Next.js generates correct URLs.

Deployment architectures

Development

  • Next.js dev server (pnpm dev)
  • watcher-rs in separate terminal (pnpm watcher)
  • Rust toolchain required to build watcher binary

Docker

  • Single container with Next.js + bundled watcher-rs
  • Supports local or S3 mode via environment config
  • Persistent /app/data volume for DB + covers
  • Runs as non-root node user

Electron desktop

  • Embedded Next.js server on localhost:3210
  • Bundled watcher-rs spawned by Electron
  • Storage mode selectable in onboarding/admin (Local Folder or S3 / R2 Bucket)
  • Per-session desktop auth token injected via x-alex-desktop-auth header
  • Optional relay tunnel for public access without port forwarding
  • Persistent config via electron-store (library path, S3 settings, relay preferences)

Technology stack

LayerTechnology
FrontendReact 19, Next.js 16
StylingTailwind CSS v4, shadcn/ui
AuthenticationNextAuth.js v5 (dual-instance: full config + Edge middleware); desktop header-based auth
DatabaseSQLite via Rust (rusqlite) bridge
IngestionRust notify (local) or S3 poller (S3 mode)
File servingSource-driver pipeline
PDF renderingPDF.js
EPUB renderingepub.js + react-reader
Metadata extractionlopdf, quick-xml, zip (Rust)
Cover generationpdfium-render + fallback renderer (Rust)
Real-timeServer-Sent Events (SSE)
Relayalex-relay (Axum + WebSocket tunnel); watcher-rs tunnel client
DesktopElectron 34, electron-store, system tray
TestingPlaywright (E2E, web + Electron), Jest (unit), Storybook + Chromatic (visual)
CI/CDGitHub Actions (CI, Docker release, Electron release, E2E, Chromatic, code review)
DeploymentDocker, Electron, Node.js 22