Project-detail
Kotlin / TypeScript Extended 2024

BookLore

A self-hosted Goodreads replacement that owns your reading data completely

The Problem

A four-location independent bookshop chain had accumulated 12,000 books across physical shelves, staff personal drives, a legacy FileMaker database, and a partially-filled Google Sheet. Three staff members had institutional knowledge of the catalogue. When one left, that knowledge left with her.

They’d tried Goodreads — the privacy model wasn’t acceptable for a business. They’d tried Calibre — too desktop-centric, no multi-user. They needed something that ran on their own infrastructure, worked for multiple simultaneous users, and didn’t require a technical background to operate.

What We Built

BookLore is a Spring Boot + React application with PostgreSQL persistence. The core extension built for this engagement was the BookDrop service: a filesystem watcher that turns a shared network folder into an automatic ingestion pipeline.

Drop an EPUB in the folder → the service detects it, extracts metadata from the file, enriches it via Google Books API, creates a reading entry, assigns it to the correct shelf, and notifies relevant staff — all without anyone touching a UI.

How It Works

The BookDrop file watcher uses Java’s WatchService API with a Kotlin coroutine wrapper:

class BookDropService( private val watchPath: Path, private val bookService: BookService, private val googleBooksClient: GoogleBooksClient ) { suspend fun watch() = coroutineScope { val watcher = watchPath.fileSystem.newWatchService() watchPath.register(watcher, ENTRY_CREATE) while (isActive) { val key = withContext(Dispatchers.IO) { watcher.take() } key.pollEvents().forEach { event -> val file = watchPath.resolve(event.context() as Path) if (file.extension == "epub") { launch { processEpub(file) } } } key.reset() } } private suspend fun processEpub(file: Path) { val metadata = EpubMetadataExtractor.extract(file) val enriched = googleBooksClient.enrich(metadata) bookService.createFromDrop(enriched, file) } }

The Google Books enrichment fills in missing ISBN, cover art, author biography, and genre classification. For books that Google Books doesn’t recognise (rare, self-published, or very old stock), a fallback OCR pipeline reads the cover image and attempts title/author extraction.

The Outcome

12k books migrated
3 days full migration time
100% staff adoption (week 1)

The FileMaker export was converted to EPUB-wrapped metadata stubs and fed through BookDrop. 12,000 entries processed in under 72 hours with one staff member monitoring the ingestion queue. The remaining unknowns (about 340 books without ISBNs) were handled via the manual entry UI over the following two weeks.

The client now runs BookLore on a Raspberry Pi 5 in the back office. Staff search, recommend, and track reading across all four locations through a shared web interface. No external dependency, no subscription, no data leaving their building.