Skip to content
helenvholmesPublic

About

A free, self-hosted reading log with offline mode, instant search and Spotify audiobook progress.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

BookBox

Support me on Ko-fi

A personal reading log you host yourself, for free. Track what you're reading, what you've read (and re-read), who recommended what, and what you thought, with book details filled in from OpenLibrary. It installs to your phone's home screen, works offline, and can follow your audiobook progress on Spotify.

BookBox is for one reader per copy: you deploy your own, on your own free accounts, and your library stays yours. It's built with Next.js and SQLite (a local file, or Turso when hosted).

Features

  • Add books in seconds: search OpenLibrary by title, author or ISBN, or scan the barcode on the back with your phone's camera. The title, author, description, publisher, year, pages and cover fill in automatically.
  • Search as you type: titles, authors (including names in their original script), series, tags, ISBNs, and your reviews, quotes and notes. Accents don't matter, words match by their start, and small typos are forgiven ("kill crek" finds Kill Creek).
  • Shelves and filters: Currently Reading, To Read, Read and Abandoned, plus year read, tag, person, rating, and owned/Kindle/borrowed.
  • Lists: your own collections, like Urbanism or Gifts to give, in the sidebar. Add a book from its page; a book can be on any number. A tag (such as an imported Goodreads shelf) can be turned into a list in one step.
  • Reads and progress: every read is logged separately, so re-reads are tracked. Progress is set by page (or by percentage for Kindle books) and shows on your Currently Reading shelf.
  • Spotify audiobooks (optional): when you start or resume an audiobook on Spotify, its book moves to Currently Reading (and is added if it isn't in your library yet), with your listening progress.
  • Offline: once opened online, the home screen app keeps working without a connection. Search, covers and the main pages stay available, and changes to status, ratings, reads, progress and notes are saved on your device and sync when you're back online.
  • People and sharing: note who recommended a book and who it's for, and give anyone a read-only page ("Books for Katie") at an unguessable link, with its own title, message, layout, accent colour, and whether your ratings and reviews show. Private notes and spoilers are never shared.
  • Stats: a reading goal per year, books per year, ratings, top tags and authors, how many of the books you read were written by women, men and nonbinary authors (looked up on Wikidata, and yours to correct on the Authors page), and whose recommendations you rate highest, plus a reading calendar and a year in review for every year.
  • Series (optional, with Hardcover): each series page lists the books in the series you don't have yet, from Hardcover, with a button to add one to To Read. Series Hardcover doesn't know are left off the Series page.
  • Lending: note who has your copy of a book and when it's due back, with a history of past loans.
  • Tags (rename, merge, delete), library borrowing with due dates, and tidy-up tools in Settings for missing covers, missing ISBNs, duplicates and a bulk OpenLibrary refresh.
  • Import from Goodreads: bring in your Goodreads export (books, shelves, ratings, reviews and finish dates) from Settings. Books you already have are flagged so you choose which to add, and covers are fetched from OpenLibrary.
  • Import from Libby: bring in your Libby timeline export (books you've borrowed, returned and placed holds on) from Settings. Returned books count as read on the day you returned them, unless you mark them as not finished.
  • Export your whole library as CSV at any time.

Deploy your own (free)

BookBox runs within the free tiers of Vercel (the app), Turso (the database) and Cloudflare R2 or Vercel Blob (cover storage). You'll need a GitHub account and about ten minutes.

  1. Fork this repository on GitHub.
  2. Create a Vercel project from your fork (Vercel → Add New → Project → import the repo). The first deploy will show a locked app until the next steps are done; that's expected.
  3. Add a database. In the project, open Storage → Create Database → Turso, pick the free plan, and connect it to the project. That sets TURSO_DATABASE_URL and TURSO_AUTH_TOKEN. (Or create a database at turso.tech yourself and set those two variables.)
  4. Add cover storage. Either:
    • Cloudflare R2 (more room: 10 GB and millions of reads a month free). In the Cloudflare dashboard, create an R2 bucket (leave public access off), then under R2 → Manage API tokens create a token with Object Read & Write on that bucket. Add R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY and R2_BUCKET to the Vercel project's environment variables.
    • Vercel Blob (fewer steps). Still under Storage, create a Blob store with Private access and connect it. That sets BLOB_READ_WRITE_TOKEN. Its free tier allows 10,000 reads a month; past that, Vercel blocks the store for 30 days, so this suits a single small library.
  5. Set a setup password and a secret. Under Settings → Environment Variables, add BOOKBOX_PASSWORD (a long passphrase) and BOOKBOX_SECRET (a long random string, for example from openssl rand -base64 32). Mark both Sensitive.
  6. Redeploy (Deployments → ⋯ → Redeploy), open your site, and sign in with your email and that password. The first sign-in creates your account; after that the email and password are your sign-in, and the password can be changed in Settings. The database is set up on first use. Then add your name in Settings too.

Your library is at /library. Visitors who aren't signed in get a page about BookBox, with links to sign in and sign up.

Inviting family and friends

The owner (the first account) can invite people from Settings → People: each invite link works once, for two weeks. Everyone who joins gets an empty library of their own, in a Turso database BookBox makes for them, which nobody else can see. That needs a Turso Platform API token: in Turso's dashboard (from Vercel, open your database and choose Open in Turso), make one under Settings → API Tokens, and set TURSO_API_TOKEN (Sensitive) and TURSO_ORG (the name in the dashboard's address) in Vercel. Turso's free plan has room for a few hundred databases.

Covers for everyone share your cover storage and its free tier. Each library can keep up to 5,000 covers. To connect Spotify, each person needs adding to your Spotify app's users (up to 25).

On iPhone, open your site in the browser, tap Share, then Add to Home Screen. The home screen app keeps its own sign-in, so you sign in once inside it.

Environment variables

These are for the server: the password and the services BookBox runs on. Preferences, like your name, are on the Settings page in the app and saved with your library.

Variable Required What it's for
BOOKBOX_PASSWORD Yes, when hosted The password for the first sign-in, which creates your account (with the email you enter). Not used once an account exists. When deployed on Vercel, the app stays locked until it's set.
TURSO_DATABASE_URL, TURSO_AUTH_TOKEN Yes, when hosted The database. Without them, BookBox uses a SQLite file in DATA_DIR.
R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET Yes, when hosted (or Blob) Cover storage in a Cloudflare R2 bucket. Used over Blob when both are set. Without either, covers are files in DATA_DIR.
BLOB_READ_WRITE_TOKEN Yes, when hosted (or R2) Cover storage in a private Vercel Blob store.
STORAGE_CYCLE_DAY No The day of the month your storage service's billing period starts (Cloudflare's Billing page shows it; default 1). BookBox counts cover uploads and reads per period and pauses them at 80% of the free tier; Settings shows the counts.
BOOKBOX_SECRET Recommended Signs the sign-in cookie. Defaults to BOOKBOX_PASSWORD; changing it signs everyone out.
ACCOUNTS_DATABASE_URL, ACCOUNTS_AUTH_TOKEN No A separate Turso database for accounts. Without them, accounts are kept in the library's database.
TURSO_API_TOKEN, TURSO_ORG, TURSO_GROUP To invite people A Turso Platform API token and your organization's name (and its group, default if unset), so BookBox can make a database for each person who joins.
SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET No Spotify app keys, if you'd rather set them here than in Settings; see below.
HARDCOVER_TOKEN No A Hardcover API token, if you'd rather set it here than in Settings; see below.
DATA_DIR No Where the local database and covers live (default ./data).

Changing your password in Settings signs out your other devices.

Spotify (optional)

Spotify only lets each developer app serve a handful of users, so everyone uses their own. It's free, but the Spotify account that owns it needs Premium, and Spotify only offers audiobooks in some countries.

  1. In BookBox, open Settings → Spotify. It shows the redirect URI for your site.
  2. On the Spotify developer dashboard, create an app, tick Web API, and add that redirect URI.
  3. Paste the app's Client ID and Client secret into Settings, then tap Connect Spotify.

BookBox only reads your saved audiobooks and listening positions; it never plays or changes anything. It checks when you open the app, at most every few minutes. The first check only records where each audiobook stands, so audiobooks you'd already started aren't all pulled in at once. Add any of them from the Spotify page (linked from Settings), which lists your audiobooks and what they're linked to.

(You can set the keys as SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET on the host instead; those win over Settings.)

Libby (optional)

Libby has no API for other apps, so BookBox uses the private one the Libby app itself uses (as odmpy and the Libby plugin for Calibre do). It can stop working whenever Libby changes it.

  1. In the Libby app, open Settings → Copy To Another Device and pick the option that shows an 8-digit code (Libby's help has the steps).
  2. In BookBox, open Settings → Libby, type the code within a minute, and tap Connect Libby.

BookBox checks your shelf when you open the app, at most every ten minutes. A new loan moves its book to Currently Reading (adding it if it isn't in your library yet) with its library and due date. When a loan goes back, by you or because it was due, the book moves to Read that day if it's still Currently Reading: Libby doesn't say whether you finished, so change any you didn't to Abandoned. New holds you don't have yet go on To Read. The first check takes your shelf as it is, but leaves books you've already marked Read or Abandoned alone. Magazines are skipped.

BookBox only reads your loans and holds. The connection could borrow and return on your cards, though, so it's kept on the server and never copied by pull:cloud. If Libby signs BookBox out, Settings says so; connect again with a new code. For your history from before you connected, use the timeline import below it.

Hardcover (optional)

Series pages can show the books in each series that you don't have yet, using Hardcover's series data.

  1. Sign in to Hardcover (a free account is fine) and open Hardcover API in your account settings.
  2. Make a token and paste it into Settings → Hardcover. BookBox checks it with Hardcover before saving it.

Each series is looked up once and remembered; the Series page checks a few new ones per visit to stay within Hardcover's rate limits. Hardcover tokens expire, so make a new one if series pages say it was refused. (You can set the token as HARDCOVER_TOKEN on the host instead; that wins over Settings.)

Running it on your computer

npm install
npm run dev

With BOOKBOX_PASSWORD unset there's no sign-in, which is fine on your own machine. The database and covers go in ./data (ignored by git). See .env.example for every setting.

The service worker (offline support) only runs in production builds, so npm run dev always shows fresh code. Try offline mode with npm run build && npm start.

Tests

npx playwright install chromium   # once
npm test

The tests drive the app in a real browser through everyday actions: adding a book by hand, finding one by search and saving it with its cover (also when OpenLibrary's cover server is down), editing and deleting. Each run starts its own copy of the app on port 3210 with an empty library in e2e/.data, and never reaches the real book catalogs: e2e/catalog-stub.mjs answers for them from e2e/fixtures.mjs. Stop npm run dev first, as Next.js runs one dev server per project. Add a test to e2e/ when you add or change a common action.

Moving a local library to your hosted copy

If you've been using BookBox locally first, copy it up once, before you start using the hosted copy (this replaces what's in the hosted database, and uploads every cover that isn't there yet):

TURSO_DATABASE_URL=libsql://… TURSO_AUTH_TOKEN=… R2_ACCOUNT_ID=… R2_ACCESS_KEY_ID=… R2_SECRET_ACCESS_KEY=… R2_BUCKET=… npm run push:cloud

(Use BLOB_READ_WRITE_TOKEN=… in place of the R2 variables for Blob.) To move covers from Blob to R2, run npm run pull:cloud first so every cover is on your computer, then push:cloud with only the R2 variables.

Keeping a local copy up to date

To work on BookBox against your real library, copy the hosted one down (the local database is backed up to data/backups first):

npm run pull:cloud

It uses the same keys file as push:cloud, saved as .env.cloud (git-ignored) with TURSO_DATABASE_URL, TURSO_AUTH_TOKEN and the cover storage keys (the four R2_ variables, or BLOB_READ_WRITE_TOKEN). With that file in place, npm run dev also shows a Pull latest notice whenever the local copy is more than three days old. Your Spotify sign-in and other server-only records stay on the server.

Importing

scripts/import-airtable.mts imports the Airtable base BookBox was first built to replace; it's a starting point for writing an importer for your own spreadsheet.

Home screen assets

npm run generate:icons redraws the app icon (defined as SVG in scripts/generate-icons.mts) and writes the manifest icons, favicon and iOS launch screens.

Hosting elsewhere

Any host with a persistent disk works too (Fly.io, Railway, Render, a VPS), with the database and covers in DATA_DIR:

npm run build
cp -R .next/static .next/standalone/.next/static && cp -R public .next/standalone/public
DATA_DIR=/path/to/data BOOKBOX_PASSWORD=… node .next/standalone/server.js

Always set BOOKBOX_PASSWORD on a host others can reach.

Feature requests

Ideas for BookBox go in Feature requests on GitHub Discussions. Search first, and give an existing request a 👍 rather than posting it again: the most-wanted ideas get built first. Bugs go in issues.

Support BookBox

BookBox is free and open source. If it's useful to you, you can leave a tip on Ko-fi.

Security

  • One password protects everything except share pages, which are only reachable by their unguessable links and never include private notes or spoilers. Covers are served through the app, so they're private too.
  • After 10 wrong passwords from one network address in 15 minutes, sign-in from that address pauses for the rest of the 15 minutes.
  • Found a problem? Please open an issue, or for anything sensitive, contact the maintainer privately rather than posting details publicly.

License

AGPL-3.0. You're free to use, change and share BookBox. If you run a modified copy for other people to use, you have to offer them its source code under the same license. The "BookBox is open source" link in the app's sidebar is there for that: point SOURCE_URL in src/lib/constants.ts at your fork.

About

A free, self-hosted reading log with offline mode, instant search and Spotify audiobook progress.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Sponsor this project

Used by

Contributors

Languages