Independent project
One Library for Every Manga Source, on iOS and Android
A self-hosted manga reader with native iOS and Android apps over a Rust backend, unifying many sources into one private library that remembers exactly where you are.
I kept an old Android phone alive just to read manga, because the iOS readers were not good enough. That is a silly reason to carry a second phone, so I built the reader I wanted.
Atlantic Forge
The stakes
For years I kept an old Android phone alive for exactly one job: reading manga. The readers on iOS are missing too many of the small comforts that matter across a long series, so the second phone stayed. And even on Android, the good options were not good: customisation that stops one setting short of what you actually want, and ads interrupting the one activity that is supposed to be a pleasure. Meanwhile the manga itself lives scattered across dozens of sources, each with its own cluttered site, its own catalogue, and no memory of where you left off.
By this point on the page a pattern is showing: the tool I wanted did not exist, so I built it. But “one good reader” is a harder promise than it sounds. It means a backend presenting a calm, stable catalogue on top of a messy, shifting world of sources, and two native clients that must agree with it and with each other, perfectly, forever. Three codebases describing one library is three chances to drift, and a reader notices drift immediately: a bookmark that exists on the phone but not the tablet, a chapter marked read that comes back unread.
The move
The agreement is enforced, not hoped for. Both native clients share byte-identical GraphQL queries against one schema, so if the contract changes, both apps feel it at build time rather than the reader feeling it at night. And the backend keeps every ugly detail backstage: aggregation, failover, image caching. A source can slow down, change, or vanish, and neither app changes a line.
The apps ask for a series, never a source. The mess stays backstage where it belongs.
The other deliberate choice was who it is for. Paneru is self-hosted, with accounts for a small circle of invited readers, not a public service. That keeps the system honest about its purpose: a private library, run on hardware its owner controls, serving the people it was built for.
What shipped
A self-hosted system in three parts. A Rust backend (Actix, async-graphql, Postgres) aggregates MangaDex and several other sources into a single catalogue behind one GraphQL API, with an image proxy, WebP caching, and JWT accounts. On top, two native clients: iOS in SwiftUI (iOS 17+) and Android in Kotlin with Jetpack Compose. Both give one unified library, paged and webtoon reading, offline downloads that feel identical to reading online, cross-device progress sync, bookmarks, a continue-reading shortcut, and new-chapter notifications. The design follows a single rule: covers are the interface while browsing, and the reader itself is silent, with an AMOLED-black mode, a brightness override, and warm backgrounds for long night sessions. In other words, the small comforts the second phone was kept alive for, finally in one place, with no ads anywhere.
The stack, and why
Rust, rather than Node or Python. The backend’s whole job is concurrent, unglamorous work: scraping several sources at once, proxying and caching thousands of images, and staying up unattended on a small self-hosted box. A Node or Python service would have been quicker to write, and for a first prototype that trade is often right. Here the service runs unwatched on modest hardware, where Rust’s compile-time guarantees and small footprint are worth the slower start: no runtime to tune, no memory creep to chase, and a class of concurrency bugs that cannot ship because they cannot compile.
SwiftUI and Jetpack Compose, rather than Flutter or React Native. A reader lives or dies on feel: the scroll physics of a long webtoon strip, image loading that never stutters, and deep platform integration for downloads and notifications. Cross-platform frameworks are honest about their trade: one codebase, at the cost of living one step from the platform. For a content app that trade can be right; for a reading surface it is the product itself being compromised. Two native apps cost more to build, and the shared GraphQL contract claws most of that cost back by keeping their logic identical.
Postgres, rather than a document store. A manga library looks like documents until you describe it: series have chapters, chapters exist on several sources, readers have progress per chapter and bookmarks per series. Those are relations and joins, and Postgres answers them directly. A document store would push those joins up into application code, which is exactly where a three-codebase system does not need more logic to keep in agreement.
What it changed
- One private library replaced a weekly ritual of tabs, searches, and lost places
- Reading feels the same offline as online, and progress follows the reader across devices
- The sources can shift and fail behind the API without either app noticing
- Both platforms get a genuinely native reader, not a wrapped website