A small website for finding the Dewey Decimal number of a book. Type an ISBN (10 or 13, hyphens fine) or scan the barcode with your camera, and get back the shelf number plus title, author and cover.
Live: https://dewey.chillavillekulla.cc
The interesting part is that no single catalogue knows every book, and the ones that do know a book often disagree about how precise they are willing to be. So Dewey asks four of them at once and picks the best answer.
One Cloudflare Worker serves the whole thing: the React app as static assets, and a single API route.
browser (React SPA)
| GET /api/lookup?isbn=<10 or 13 digits>
v
worker/index.ts
| normalises to ISBN-13, then queries 4 catalogues in parallel
| (Promise.allSettled, shared 8s AbortSignal.timeout)
v
{ isbn, results: BookResult[] } one entry per catalogue that knew the book
Picking the primary answer is a small heuristic: the source whose Dewey number has the most digits wins, so a full 745.2 beats the German National Library's coarser 740. If nobody has a number but someone knows the book, the card says so rather than pretending. The other sources stay visible as "X agrees" or "X says 155.24", because disagreement is information too.
Search history lives in localStorage. Nothing is stored server side.
Each lookup returns null if that source has never heard of the book.
Biblioteksentralen (Bibbi) has the best metadata for Norwegian books: Dewey number, cover, publisher, contributor roles. It writes names as "Surname, First", so they get flipped on the way out.
The National Library of Norway covers everything under Norwegian legal deposit, but the Dewey number is not in the catalogue response. It takes a second fetch of the record's MODS document to find <mods:classification authority="ddc">.
Open Library brings international coverage. Its ddc field needs filtering, since it contains values like [Fic] alongside real numbers.
The German National Library answers over SRU with MARC21 XML, where Dewey lives in field 082 subfield a, carrying / segmentation marks that need stripping. It usually returns a coarse three digit Sachgruppe, which is why the specificity heuristic exists.
Both of the last two are parsed with regex rather than an XML parser, because Workers have no DOMParser and these feeds are well formed enough not to need one.
Worth recording, because most of these look promising until you actually call them:
- OCLC Classify would have been ideal. It shut down in 2023.
- Library of Congress has excellent Dewey data, but ISBN search is only available over SRU on port 210, which Cloudflare Workers cannot open. Their JSON API does not index ISBNs at all.
- Harvard LibraryCloud has no working ISBN lookup.
- K10plus returns records, but field 082 rarely carries the actual number.
- Finna sits behind a bot challenge.
- The British Library SRU endpoint has been dead since their 2023 cyberattack.
The lesson: coverage claims in API documentation are close to worthless. Every source here was checked against a Norwegian book, an English book, and something obscure before it earned a place.
Scanning uses the barcode-detector ponyfill on ZXing WASM rather than the native BarcodeDetector, so it works on iOS Safari and Firefox instead of Chrome only. The WASM is self hosted rather than pulled from a CDN at runtime.
The scanner and its roughly 1 MB of WASM are lazy loaded, so they cost nothing until someone presses Scan. Scans only accept EAN-13 codes starting 978 or 979 with a valid check digit, so pointing the camera at a cereal box does not fire off a lookup.
Cameras need HTTPS, or localhost while developing.
bun install
bun run dev
bun run test # ISBN normalise, validate and convert
bun run typecheck
bun run lintWhere things live: worker/index.ts is the fan-out and one lookup function per catalogue, shared/isbn.ts handles ISBN-10 to ISBN-13 conversion with a recomputed check digit, src/app.tsx is the entire UI, and src/scanner.tsx is the camera dialog.
/api/lookupresponses are cached for a day. While changing lookup logic, re-searching the same ISBN will show a stale answer until you force a reload.- The Worker deliberately carries no Cloudflare type package. Its surface is small enough to type inline against DOM types, which keeps the project on one tsconfig.
- The visual theme is a library card catalogue: bottle green desk, ivory index card, red margin rule, punched hole. Fonts are self hosted.