MangaRead
A manga reader that scrapes chapters from third-party sites, merges them into series, and serves them through a web interface.
What it does
MangaRead scrapes manga chapters from third-party scanlation sites, merges them into unified series entries, and serves everything through a local web interface. You can bookmark series, track reading progress, and the server auto-syncs new chapters in the background.
Architecture
Three Rust crates in one workspace:
- mangaread-core — domain types, SQLite repository, series merging logic
- scraper — SiteAdapter trait with per-site implementations (Demonicscans, Manhuaplus), CLI binary + library
- website — Axum web server with 10 route modules, Maud templates, static assets
The database uses SQLite with WAL mode. Four tables: series, manga, chapters, pages. All inserts use ON CONFLICT ... DO UPDATE so the whole thing is safe to re-run.
Chapters from different sources get linked to one unified series by normalized title. If two sources have the same chapter, the one with the most pages wins.
flowchart TB
CLI[ScraperEngine] --> DB[(SQLite)]
WEB[Axum Server] --> DB
WEB <--> BROWSER[Browser]
BROWSER --> JS[app.js + localStorage] The hard parts
Chromedriver. The scraper uses a real browser to render JavaScript-heavy manga sites. Getting chromedriver to behave consistently in Docker was easily 30% of the development time. Headless mode, sandbox issues, zombie processes — the usual webdriver horror show.
SQLite and threading. rusqlite's Connection is not Sync, so each scraper thread opens its own connection. The main AppState wraps its connection in a Mutex. This sounds simple, but the borrow checker had opinions.
Client-side state without accounts. No authentication, no database sessions. Bookmarks and reading progress live entirely in localStorage. This means the JavaScript reads from localStorage and builds the DOM via a tiny h() helper. It's surprisingly clean but debugging state is harder when you can't just query a database.
What I learned
This was my first real Rust project with multiple workspace crates, and it taught me more than any tutorial. I learned that Rust's type system is incredible for domain modeling (no invalid states possible) but also that fighting the borrow checker for the 50th time at 2am is a rite of passage.
The scraper adapter pattern (trait + per-site implementations) taught me that good abstractions pay off immediately when you add a second source. The sync loop design (background thread, 6-hour interval, parallel scraping) taught me about Tokio channels and graceful shutdown.
Also: always pin your chromedriver version. Always.