Livra
A whole reading app, built alone: iOS, Android and web, with 600 registered users in its first month. Underneath, six catalog APIs converge on one canonical book identity.
- registered users in the first month
- 600registered users in the first month
- platforms: iOS, Android and web
- 3platforms: iOS, Android and web
- Cloud Functions in the repo
- 203Cloud Functions in the repo
- person: design, front, back and native
- 1person: design, front, back and native
- Client
- My own product
- Role
- Design, front-end and serverless back-end
- Year
- 2026
- Stack
- React 19Vite PWAFirebaseCloud FunctionsCapacitorStripeGemini
- In production
- uselivra.com ↗
What I owned
- 01Everything, alone: product, design, front-end, back-end and operations.
- 02One canonical book identity across six catalog APIs, with the cache and cost rules that keep search cheap.
- 03The in-app economy (coins, XP, streaks): written only by Cloud Functions, never by the client.
The app, annotated
Screens from the version in the stores. Each numeral points at something on screen and says what runs underneath it.
Production screenshots.
- 30: Streak. Only Cloud Functions write it, in idempotent transactions keyed by user, reason and day.
- 32: Physical books track pages, Kindle tracks percent. One progress model, two inputs.
- 34: Logging progress is what feeds the streak, the XP and the weekly league.
- 36: Lura, the AI librarian. Gemini picks, and every pick is checked against the real catalog before it becomes a card.
- 40: XP and position are computed on the server. The client never writes money or rank.
- 42: Promotion and relegation zones for the week: who goes up, who stays, who drops.
- 44: Coins are paid by final position. A fixed contract since real users arrived: changing it takes a documented decision.
- 46: Four divisions, Bronze to Diamond.
- 50: A timed reading session.
- 52: Tied to the book and the page you're on, so finishing a session logs progress.
- 54: Ambient sound. Two of them unlock with coins earned in the app.
- 60: Pick by mood: for you, light, suspense, fantasy.
- 62: Every pick is a book that exists in the catalog. The model can't invent a title.
- 64: The reason is written for this reader, from what's already on their shelf.
Streak. Only Cloud Functions write it, in idempotent transactions keyed by user, reason and day.
The shortest way into the catalog
A barcode, decoded on the phone, then the same reconciliation that search uses. Recorded on a device.






FIG. 4eScanner, batch mode, from the camera to the shelf.
Point at the barcode on the back cover. It's decoded on the phone. No barcode? “Photograph the cover” sends it to multimodal AI instead.
Problem ·No single book database works for Brazil. Each one gets something wrong, so the same book shows up broken, or twice.
FIG. 4Four of six catalog sources converging into one canonical work.
Four answers for one book
Every source answers; none is fully right. Reconcile picks each field from the source that's trusted for it. Then take CBL down.
Simulation. Illustrative data, real mechanism.
Google Books
- title
- Torto arado
- author
- Itamar Vieira Junior
- publisher
- Todavia
- pages
- —
- cover
- thumb 128px
- ids
- google-…
Apple Books
- title
- Torto Arado
- author
- Itamar Vieira Junior
- publisher
- —
- pages
- ✓
- cover
- hi-res
- ids
- apple-…
CBL
- title
- Torto arado
- author
- Vieira Junior, Itamar
- publisher
- Todavia
- pages
- ✓
- cover
- catalog card
- ids
- isbn:978…
Open Library
- title
- Torto arado
- author
- Itamar Vieira Junior
- publisher
- —
- pages
- —
- cover
- —
- ids
- OL…
Canonical record
Four partial answers. Nothing to show yet.
One layout, four palettes
Themes are sold in the app. Every screen re-skins, from the profile to the league.
Production screenshots.

Linho

Café

Alcova

Aurora
Context
Brazilian readers are stuck between catalogs that don't talk to each other: Skoob has the Brazilian titles but the product stalled years ago; Goodreads ignores Brazil; neither has a real habit loop. Livra is a gamified reading journal (streaks, weekly leagues, a coin economy, focus mode) built for readers who already turn books into content on TikTok and Instagram.
The core technical problem
No single book-data source works for Brazil. Google Books has volume but is noisy and doesn't return page counts in search; Apple has the best covers but needs its locale forced; CBL is the authority on Brazilian editions but sometimes serves the catalog card as the cover; Open Library is only a fallback. None of them solves it alone. The orchestration is the product.
What I built
A unified search that queries up to 6 sources in parallel, detects the query type (ISBN vs. free text) and reconciles everything into a canonical work × edition identity: the same book arriving as apple-X, google-Y or isbn:Z converges on a single document. That prevents duplicate cards, fragmented reviews and inconsistent covers. Ratings are keyed by work, not edition, so different editions share the same reviews.
Around the core: a barcode-first scanner (local ZXing decode, zero cost) with multimodal AI only as a cover-photo fallback; recommendations generated by Gemini but validated against the real catalog before they become cards; and a self-healing catalog: opening a book with thin metadata triggers background enrichment, so browsing itself improves the database.
Why it's built this way
- Server-only economyCoins, XP, streaks and PRO status are only ever written by Cloud Functions, with idempotent transactions keyed by
{userId}_{reason}_{dayKey}. The client is never trusted with money — not even mine. - The “never cache an empty list” ruleA failing source returning
[]would poison the cache for its entire TTL, making books invisible. Every cache layer validates before persisting. - Cost as a requirement~40 reads per search were replaced by layered caches, each with its own TTL (5 min ISBN, 24 h text, 7 days CBL). The Google Books quota became an observable metric: a production 503 taught me the difference between a daily quota and a soft rate limit, and the fix was staggered requests plus a circuit breaker, not “retry harder.”
- Defense in depth as a patternEvery bug is fixed at the source (the server), backed by a client-side defense (self-healing) and a cleanup script for data already written. That lets a live catalog evolve without painful migrations.
Status
Live on the App Store, Google Play and the web (version 1.10 in the stores). 600 registered users in the first month, on an app built and run by one person. The metrics I watch are engineering metrics: cost per search, identity convergence, economy integrity.