A self-hosted, browser-based personal book library manager running on port 8080.
- FastAPI — backend and routing
- Jinja2 — server-side HTML templates
- SQLite — local database (no external DB required)
- Docker / Docker Compose — containerised deployment
- Book list with full-text search (title and author)
- Filters: Reading State, Year Read, Location (and any additional dynamic filter you add)
- Add, edit, and delete books
- Import books from an Excel file (
.xlsx) - Stats strip at the top: total, read, not read, reading
- Export filtered list as CSV or Excel
All fields are optional except Title.
| Field | Notes |
|---|---|
| Title | Required |
| Author | |
| Release Date | |
| Worth Reading | |
| Reading Status | Free text or dropdown — drives the Reading State pill |
| Year Read | |
| Location | Where the book is physically or digitally stored |
| ISBN | |
| Current Page | |
| Total Pages | |
| Rating | |
| Description | |
| Notes |
The app stores two separate fields:
status_raw— the original text value (e.g."Read","Not Read","Reading")reading_bucket— the normalised value used for filtering and the coloured pill (read,not_read,reading,other)
reading_bucket is computed automatically from status_raw on every save. Recognised values:
| Type in Reading Status | Pill shown |
|---|---|
Read |
Read |
Reading |
Reading |
Not Read / To Read |
Not read |
| anything else | Other |
docker compose up --buildThen open in your browser:
- http://localhost:8080
- or http://YOUR-SERVER-IP:8080 from another device on the LAN
- Place your
.xlsxfile in theimports/folder, or upload it directly via the UI. - Go to Manage → Import Excel in the app.
- The importer maps the
Statuscolumn from the spreadsheet tostatus_rawautomatically.
Duplicate handling: a book is skipped if an entry with the same title + author pair already exists in the database.
To expose a new filter in the UI (e.g. Location, Rating):
main.py— add the parameter tohome(), query distinct values, filter the queryset, and pass the list to the template.index.html— add a<select>element in the filter toolbar using a{% for %}loop over the new list.index.html— add the new parameter to the sort column links (¶m={{ param|urlencode }}).
docker compose downTo also remove the database volume:
docker compose down -v