Browse docs

Architecture

Current system components, data flows, and deployment surfaces

Alex is a self-hosted PDF and EPUB library with web, Docker, and Electron delivery options. The source repository is a pnpm workspace containing the application, a shared UI package, and the optional Cloudflare relay.

System overview

Rendering diagram...

Workspace packages

PathResponsibility
Repository rootNext.js app, Electron shell, scripts, tests, and packaging
packages/ui (@alex/ui)Shared Radix-based components, style tokens, utilities, and stories
watcher-rsRust ingestion engine, SQLite bridge, S3 streamer, and tunnel client
alex-relay (@alex/relay)Cloudflare Worker, Durable Object, protocol codec, and Worker tests

Next.js application

The App Router serves the authenticated library, collections, readers, admin pages, public shared collections, and JSON/streaming APIs.

The two readers are client-only dynamic imports:

  • PDF — PDF.js through react-pdf; continuous page scroll, page tracking, zoom, fit, and document-wide search
  • EPUB — epub.js through react-reader; continuous single-column scroll, table of contents, chapter navigation, responsive app theming, and saved font size

Tailwind CSS v4 styles application UI. Reusable primitives and CSS tokens live in @alex/ui; app-specific readers and library components remain under src/components.

Authentication

Alex supports browser and Electron requests on the same server:

  • Browser users sign in with email/password through NextAuth v5. Passwords are bcrypt hashes and sessions are 30-day JWTs.
  • Middleware uses a lightweight NextAuth configuration to decode the same JWT and enforce protected/admin routes.
  • Electron generates a random token for each app process and injects x-alex-desktop-auth only into requests to 127.0.0.1:3210. Those requests use a synthetic admin session.
  • Remote requests arriving through the desktop relay do not have the Electron header, so normal Alex login still applies.
  • Shared collection pages and APIs bypass login, then validate their UUID token and book membership.

Secure cookie prefixes are selected from NEXTAUTH_URL. Forwarded host/protocol headers keep redirects and login cookies correct behind the relay.

SQLite and the Rust bridge

The web process does not load a native Node SQLite module. src/lib/db/rust.ts starts a short-lived watcher-rs db process for each query or mutation, sending SQL as JSON and receiving JSON rows. The ingestion watcher uses rusqlite directly against the same database.

Connections use WAL mode, foreign keys, and a five-second busy timeout. SQL migrations live under src/lib/db/migrations; scripts/db-push.js creates the initial schema, repairs the unique book indexes for older databases, and adds S3 source columns when needed.

Ingestion

watcher-rs auto-selects one mode:

  • Local when S3_BUCKET is absent: recursively scan/watch LIBRARY_PATH, wait for writes to become stable, then handle additions, changes, and removals.
  • S3 when S3_BUCKET is present: list the configured bucket/prefix on startup and on the polling interval, then diff keys and ETags against S3-backed book rows.

For PDF and EPUB files, the pipeline computes a SHA-256 hash, extracts metadata, writes a JPEG cover, updates SQLite, and changes settings.library_version. The authenticated SSE endpoint polls that value every two seconds so clients can refresh after ingestion changes.

Cover generation uses:

  • PDF page 1 rendered at 150 DPI with PDFium
  • Embedded EPUB cover art re-encoded as JPEG
  • A synthetic 400×600 JPEG fallback with title and author

Book file serving

Private and shared reader routes use src/lib/files/serve-book-file.ts:

  • Local books stream from disk with HTTP Range support.
  • S3 books stream through the Rust s3-stream command with Range passthrough.

Files stay in object storage in S3 mode. Alex stores metadata and covers locally, and browsers do not need bucket credentials or CORS access.

Electron desktop

In a packaged desktop app, Electron manages:

  1. A standalone Next.js server on 127.0.0.1:3210
  2. The local or S3 ingestion watcher after onboarding
  3. An optional watcher-rs tunnel process when Public Access is enabled

The app stores a small config.json in Electron's user-data directory. It contains the library/S3 selection, generated NextAuth secret, window state, and relay settings. The SQLite database and cover directory also live under user data. This is a custom JSON store, not the electron-store package.

Public-access relay

alex-relay is a TypeScript Cloudflare Worker backed by hibernatable Durable Objects. The Worker routes each public hostname and tunnel WebSocket to the object named for that subdomain. One desktop socket registers with the object; concurrent browser requests are assigned IDs and multiplexed over its binary WebSocket.

The Rust client forwards each request to the local Next.js server, adds forwarded host/protocol headers, and streams the response in 64 KiB chunks. The Worker caps request bodies at 10 MiB and uses a 120-second inactivity timeout by default.

The relay removes the need for port forwarding, but it is not an authentication bypass. Normal library pages still require Alex credentials. Public shared-collection links are the only unauthenticated reader surface.

Deployment surfaces

SurfaceCurrent state
Local developmentNext.js dev server plus watcher-rs in another terminal
DockerNode 22 standalone server and prebuilt Rust binary; amd64 and arm64 images
macOS desktopSigned/notarized Apple silicon DMG and ZIP
Linux desktopx64/arm64 AppImage and deb packaging
Windows desktopNSIS x64 packaging is configured, but the current release workflow does not publish it
RelayCloudflare Worker + SQLite-backed Durable Objects, deployed with Wrangler

Technology stack

LayerTechnology
ApplicationNext.js 16, React 19, TypeScript
UI@alex/ui, Radix UI, Tailwind CSS v4, Storybook
AuthNextAuth v5 credentials/JWT and Electron header auth
DatabaseSQLite through Rust rusqlite commands
IngestionRust notify or S3 polling
ReadersPDF.js / react-pdf; epub.js / react-reader
DesktopElectron 34, custom JSON settings store, system tray
RelayCloudflare Workers, Durable Objects, WebSockets
TestsJest, Rust tests/coverage, Playwright web/Electron, Chromatic