my-library
Code map
A tour of the tree, and which parts run in production versus on a laptop.
app/
layout.tsx Inter, metadata, footer
globals.css Tailwind 4 @theme tokens — the palette
icon.svg favicon; Next derives the rest
page.tsx the shelf
books/[id]/page.tsx one book
components/
BookCover.tsx 2:3 cover box + Rating
SearchBar.tsx the only client component
Pagination.tsx plain links, no JS
api/v1/books/
route.ts GET list · POST one-or-many
[id]/route.ts GET · PUT · PATCH · DELETE
db/
schema.ts users, api_tokens, books
client.ts lazy proxy + test injection
migrations/ generated SQL, committed
lib/
auth.ts bearer token → user
http.ts json() and the { error, code } shape
validation.ts zod: what a valid book is
queries.ts web reads: search, sort, page
scripts/ local only — never deployed
create-user.ts mint a user + token
create-token.ts mint another token
import-books.ts OpenLibrary lookup + mapping
enrich-library.ts the big sharded, resumable run
import-library-commit.ts shards → database
scan-local-books.ts catalogue local ebook files
ebook-meta.ts MOBI/EXTH + PDF metadata parser
export-catsby-drive.gs Apps Script, runs in Google Drive
tests/
books.test.ts 8 tests
helpers/db.ts pglite harness
postman/ collection + environment (git-ignored)Three tiers, and it matters which is which
| Runs | Where | What |
|---|---|---|
| On every request | Vercel | app/, db/, lib/. Nothing here talks to OpenLibrary or the filesystem. |
| By hand, locally | A laptop, with .env.local | scripts/. Long-running, network-heavy, and allowed to be slow because nobody is waiting on an HTTP response. |
| Once, elsewhere | Google Drive | export-catsby-drive.gs — Apps Script, pasted into script.google.com. It is in the repo because it is source, not because it deploys. |
Why the scripts are not API routes
Enriching the library is ~3,800 books at two OpenLibrary calls each, politely spaced. That is roughly an hour of wall time — far past any serverless timeout, and pointless to expose as an endpoint when it runs once. Keeping it in scripts/ also means the production bundle never carries the import code.
The files worth reading first
| File | Why |
|---|---|
lib/validation.ts | One zod object defines a book. The API parses request bodies through it and the import parses OpenLibrary responses through it, so both reject the same things. |
db/client.ts | A lazy Proxy around Drizzle: importers get a handle, but no connection opens until first use, and tests can swap in pglite before that happens. See Database. |
lib/queries.ts | Everything the website knows about reading. Paging, ILIKE search, and the Bayesian rating order live here and nowhere else. |
scripts/enrich-library.ts | The interesting one. Sharding, checkpointing and resume, because a one-hour job that cannot be interrupted is a job that never finishes. |
Sibling repositories
The clients are separate repos, one per platform, each consuming /api/v1/books: my-library-ios, my-library-flutter, my-library-rn. They are covered in Mobile clients.