my-library
Overview
A catalogue of a few thousand books: one Next.js app that is both the REST API and the website, fed by an OpenLibrary import, with three mobile clients written against it.
| Version | v0.1.0 |
| Live at | mylibrary.mblrc.me |
| Repository | github.com/mnbayan/my-library (private) |
| Stack | Next.js 15 · React 19 · Drizzle · Neon Postgres · Tailwind 4 · Vercel |
| In the database | 3,367 books · 2,111 authors |
| Tests | 8, on an in-process Postgres |
| Clients | iOS (Swift 6) · Flutter · React Native (Expo) |
Version numbers here
package.json reads 0.1.0 and there are no git tags yet — unlike kanji-buddy, where the tag is the source of truth. So v0.1.0 above is the initial value nobody has revised, not a considered release number. Worth tagging the first time this is released deliberately rather than pushed.
Why it exists
The brief was small: list the books I own. What made it worth building properly is that the list is not small — it is a few thousand titles, and typing their metadata by hand was never going to happen. So the interesting part of this project is not the CRUD; it is getting the books in, and doing it without filling the database with the wrong ones.
The API came first and was designed to be consumed by something else — which is why there are three mobile clients written against it. The website came later and, notably, does not use that API. See Architecture for why that is a deliberate split rather than an inconsistency.
The shape of it
Everything above the dashed line is one deployment. The scripts are local-only: they never run on Vercel, and nothing in the request path talks to OpenLibrary.
What’s built
| Area | State |
|---|---|
| API | Full CRUD on one resource. Batch create, three-state partial updates, bearer-token auth, every query scoped to its owner. |
| Web app | Server-rendered shelf with search, four sort orders and pagination — all in the URL. Read-only. |
| Import | A two-step OpenLibrary pipeline with a human review checkpoint, resumable and sharded across ~3,800 lookups. |
| Clients | iOS, Flutter and React Native, each covering every endpoint with its own offline test suite. |
| Tooling | A Postman collection whose tests double as a deployment smoke test. |
What’s deliberately absent
- No auth on the website. It is read-only and public; writes go through the token-protected API. One user, one shelf, and nothing on it is a secret.
- No personal rating. The only score stored is OpenLibrary’s community average. A private rating was considered and cut — it can be added later without touching the import.
- No reading status. A
want_to_read/reading/readcolumn existed early on and was removed before launch; see Database. - No pagination in the REST API. It returns the whole library in one response. The website needed paging and gets it by querying Postgres directly instead.
One user, and the design leans on it
There is a single account. Ownership is still enforced on every row and every query — the schema is multi-tenant — but there is no sign-up, no roles and no sharing. Where a decision was easier because there is exactly one shelf, these pages say so rather than pretending the design generalises.
How to read these pages
Architecture covers the stack and the one genuinely load-bearing decision; Code map walks the tree. If you only read one internals page, make it OpenLibrary import — it is where the real problems were.