ShelfWise
Book tracking and cataloging. Point your phone at a shelf, get your library indexed.
- React
- TypeScript
- Node.js
- Express
- MongoDB
Tracking books you own is easy. Going from a camera pointed at a shelf to structured records — title, author, ISBN — without retyping anything is the actual problem.
Schema-first
Every entity (Book, Shelf, Scan) is a Zod schema in @shelfwise/shared. Client form validators and server route handlers both derive from it. Adding a field is one edit in the shared package; the client and server both fail to compile until they handle it.
import { BookSchema, type Book } from "@shelfwise/shared";
// Server route — same validator as client form
const parsed = BookSchema.parse(req.body);
Mobile + web
React Native (mobile) and the Vite SPA (web) share the data layer: React Query, the shared types, and most of the business logic. Routing is split — React Navigation on mobile, React Router on web — but everything below the screen layer is portable. Platform-specific code is the camera capture flow on mobile and a drag-and-drop import on web.
Scan pipeline
Camera → spine image → OCR for title/author → ISBN lookup via OpenLibrary. The OCR layer is intentionally lossy: it returns confidence per field, and anything below threshold gets surfaced for manual confirmation before write. Easier than fixing bad reads downstream.
Testing discipline
Vitest on the client, Jest on the server, with the shared package tested in isolation. The shared tests run on every PR — refactoring a schema can't quietly break the contract because the type-test suite fails first.
Stack
Yarn workspaces monorepo: React + Vite client, React Native mobile, Express + Mongoose server, Zod shared package. MongoDB.