Full-stack reading platform · 05 / 09
Book Reader
A free e-book library: Flutter reader app, Laravel API with moderated submissions and a React admin dashboard.
- Role
- Lead developer · Flutter app with a teammate
- Year
- 2025 – 2026
- Status
- Complete
- Platform
- Android · iOS · Web admin
Overview
The project.
Three apps that work as one product: a Flutter app for finding and reading free books, a Laravel REST API, and a React admin dashboard for moderation.
It began as a book store with a cart, wallet and checkout, then I rebuilt it as a free library: e-commerce was removed, the reader moved to an open-source PDF engine and the dashboard was wired to the real API. The catalogue is seeded with 50 public-domain books.
The problem
Readers need one place to find free books, read them on a phone and keep their place, and the catalogue should grow from user submissions without unreviewed files going public.
My solution
Users submit PDFs that stay pending until an admin approves them. Approved books stream through a resilient proxy into a themed reader, and reading progress files books onto shelves automatically.
What it does
Key features.
- 01
Themed PDF reader
Resumes at the last page, with four reading themes (white, sepia, grey, night), zoom, bookmarks and four-colour highlights.
- 02
Downloads with progress
A cancellable download dialog and a local file cache, so a book opens instantly the next time.
- 03
Synced progress & shelves
Every page turn updates progress on the server, and books move to Reading or Already Read on their own.
- 04
Moderated submissions
Upload a PDF up to 50 MB with a cover. It stays pending until an admin reviews it.
- 05
Browse and search
Category chips, search by title or author, pull-to-refresh and skeleton loading.
- 06
Admin moderation
A books table with status filters and counts: approve, or reject with a reason.
- 07
Admin dashboard
Stats for users and books, category management with validation and a paginated users table.
- 08
Secure sessions
Sanctum tokens in encrypted secure storage, with a one-time migration from the old plaintext copy.
Under the hood
How it's built.
Architecture and the engineering decisions behind it.
01
One API, two clients
A Laravel 12 REST API serves the Flutter app and the dashboard with one response envelope. Routes are split into public, authenticated and admin groups, guarded by Sanctum and an admin middleware.
02
Moderation as data
Books carry a pending, approved or rejected status with a rejection reason. Only approved books are listed or streamable.
03
Files and queries
Spatie MediaLibrary stores book files and covers with thumbnails; Spatie QueryBuilder gives allow-listed filters, sorts and pagination.
04
A streaming proxy
The app always reads through /books/{id}/stream: local files are served directly and external PDFs are proxied in 64 KB chunks.
05
Typed admin client
React 19 and TypeScript with SWR for data, TanStack Table for lists, React Hook Form with Zod for forms and a persisted Zustand session.
Problems solved
Technical challenges.
Unreliable PDF hosts
Streaming resolves local files first, then external URLs through Guzzle with retries and backoff on connection errors or 5xx, clean 404s for 4xx, and chunked output.
Big uploads on a free tier
Raised PHP upload limits, size checks before upload and long client timeouts for a roughly 50-second cold start, with a clear “server is waking up” message.
Token stored in plain text
Moved the auth token to encrypted secure storage, with a one-time migration so existing users stay signed in.
Highlights that survive zoom
Swapped a paid PDF viewer for the MIT-licensed pdfrx and stored highlights as page-relative rectangles, so they redraw correctly at any zoom level.
Development status · Complete
What's next.
Everything above is built and working. These parts are planned and not implemented yet:
- EPUB reading (uploads are already accepted)
- In-app reviews (the API exists)
- Syncing bookmarks and highlights across devices
- Custom shelves