A local library catalogue app for managing your book collection. Track books, mark them as borrowed or archived, upload covers, and extract book data from photos using OCR.
Stack: FastAPI (Python) · SvelteKit (TypeScript) · SQLite · Tesseract OCR · Docker
Platform note: This guide targets macOS on Apple Silicon (M4/M3/M2/M1). The Docker images are built natively for
linux/arm64.
# Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Docker CLI, Compose plugin, Buildx plugin, and Colima (lightweight Docker runtime)
brew install docker docker-compose docker-buildx colima
# Register the Compose and Buildx plugins so `docker compose` works
mkdir -p ~/.docker/cli-plugins
ln -sfn /opt/homebrew/opt/docker-compose/bin/docker-compose ~/.docker/cli-plugins/docker-compose
ln -sfn /opt/homebrew/opt/docker-buildx/bin/docker-buildx ~/.docker/cli-plugins/docker-buildxDocker Hub serves image layers via Cloudflare CDN, which can be blocked on some networks. Configure a daemon mirror to avoid timeouts:
mkdir -p ~/.colima/default
cat > ~/.colima/default/daemon.json << 'EOF'
{
"registry-mirrors": ["https://mirror.gcr.io"]
}
EOFcolima start --dns 8.8.8.8Colima must be running before any
dockercommand. After a reboot just runcolima startagain.
docker compose up --build- Frontend: http://localhost:3000
- Backend API docs: http://localhost:8000/docs
Data (SQLite DB and cover images) persists in ./data/.
To stop:
docker compose down# Python package manager
brew install uv
# Node.js 20+
brew install node
# Tesseract OCR
brew install tesseractcd backend
uv sync
uv run uvicorn app.main:app --reload --port 8000API docs available at http://localhost:8000/docs
cd frontend
npm install
npm run devApp available at http://localhost:5173 (Vite proxies API requests to the backend)
If you already have a book catalogue in a CSV file, backend/import_csv.py can populate the database in one step.
The script expects the following column headers (the same format used in the original Babel spreadsheet):
autor/a, título, título original, editorial, traductor/a, año publicacion, año edicion, idioma, etiquetas
| CSV column | DB field | Notes |
|---|---|---|
autor/a |
Author | Required — rows without this are skipped |
título |
Title | Required — rows without this are skipped |
título original |
Original Title | Omitted if identical to title |
editorial |
Publisher | |
año publicacion |
Publishing Date | Free-text; year-only values like 1951 are fine |
año edicion |
Edition Date | |
idioma |
Language | |
traductor/a |
Notes (Translator) | Combined into the Notes field |
etiquetas |
Notes (Tags) | Combined into the Notes field |
All books are imported with status Available.
Step 1 — dry run (preview what will be imported, no changes written):
cd backend
uv run python import_csv.py /path/to/your_catalogue.csv --dry-runThis prints every line that would be added, skipped as a duplicate, or skipped due to missing data. Review the output before proceeding.
Step 2 — real import:
uv run python import_csv.py /path/to/your_catalogue.csvA summary is printed at the end:
==================================================
Added: 312
Skipped (dup): 4
Skipped (empty): 1
Errors: 0
==================================================
The script checks for duplicates in two ways:
- Against the database — any book already in the DB with the same title and author (case-insensitive) is skipped.
- Within the CSV — if the same title+author appears more than once in the file, only the first occurrence is imported.
Running the import a second time on the same file is safe — all rows will be detected as duplicates and skipped.
If the app is running via Docker Compose, copy the CSV into the container and run the script there:
docker compose cp /path/to/your_catalogue.csv backend:/tmp/catalogue.csv
docker compose exec backend uv run python import_csv.py /tmp/catalogue.csv --dry-run
docker compose exec backend uv run python import_csv.py /tmp/catalogue.csv- Click Catalogue in the sidebar, then + Add Book.
- Fill in at least Title and Author. All other fields (publisher, dates, language, notes) are optional.
- For dates, you can enter just a year (
1605), a year and month (2023-05), or a full date (2023-05-15). This is especially useful for classics that have been republished many times — use Original Publication for the first edition year and Publishing Date for the copy you own. - Click Add Book. The book appears in the catalogue.
From the book's detail page you have two options:
- Fetch Cover — Searches Open Library by title and author and downloads the cover automatically. Works best for well-known books.
- Upload — Click the Upload button to pick an image from your computer (JPEG, PNG, or WebP).
This is the fastest way to add a book when you have a physical copy in hand.
- Click Scan in the sidebar.
- Drag and drop (or click to browse) one or more photos of the book. The best shots to use are:
- The front cover — usually contains the title and author.
- The title page (inside front) — often has publisher and year.
- The copyright page (verso of title page) — has edition year, ISBN, and original publication info.
- Click Scan X image(s). Tesseract will extract the text.
- The Review & Save form appears pre-filled with whatever the OCR could identify. Check each field, correct any mistakes, and click Save Book.
- You can expand View raw OCR text to see exactly what was detected if a field looks wrong.
Tip: The cleaner and flatter the photo, the better the results. Good lighting matters more than resolution.
- Open a book's detail page and click Mark Borrowed. The book moves to the Borrowed section.
- To return it, either open the detail page and click Return, or go to Borrowed, find the book, and click the Return button on its card.
- The book's status returns to Available in the catalogue.
Use the Archived status for books stored outside the library (e.g., in boxes in storage).
- Open a book's detail page and click Archive.
- To bring it back, go to Archived and click Restore on the card, or use the Restore button on the detail page.
Every section (Catalogue, Borrowed, Archived) has a search bar that filters by title and author as you type. The search is debounced — just start typing and results update automatically.
Open any book from its card, then:
- Click Edit to modify any field, including status.
- Click Delete → Confirm Delete to permanently remove the book and its cover image.
- Book catalogue — Add, edit, delete books with title, author, publisher, dates, language, and more
- Cover images — Upload covers manually or auto-fetch from Open Library
- Borrowed books — Mark books as borrowed and track them in a dedicated section
- Archived books — Archive books stored outside the library (e.g., in boxes)
- OCR scanning — Upload photos of books to extract metadata via Tesseract OCR
- Search — Filter books by title or author across all sections
- Flexible dates — Supports year-only dates (e.g., "1605") for classics
babel/
├── backend/ # FastAPI + SQLite
│ ├── app/
│ │ ├── main.py # App entry point
│ │ ├── database.py # SQLAlchemy setup
│ │ ├── models.py # Book model
│ │ ├── schemas.py # Pydantic schemas
│ │ ├── routers/ # API endpoints
│ │ └── services/ # OCR + cover services
│ ├── pyproject.toml # Python dependencies (uv)
│ └── Dockerfile
├── frontend/ # SvelteKit
│ ├── src/
│ │ ├── routes/ # Pages
│ │ └── lib/ # Components, API client, types
│ └── Dockerfile
├── docker-compose.yml
└── README.md
See the earlier Importing from a CSV file section for the CSV import workflow and expected file format.
The script expects the following column headers (the same format used in the original Babel spreadsheet):
autor/a, título, título original, editorial, traductor/a, año publicacion, año edicion, idioma, etiquetas
| CSV column | DB field | Notes |
|---|---|---|
autor/a |
Author | Optional |
título |
Title | Required — rows without this are skipped |
título original |
Original Title | Omitted if identical to title |
editorial |
Publisher | |
año publicacion |
Publishing Date | Free-text; year-only values like 1951 are fine |
año edicion |
Edition Date | |
idioma |
Language | |
traductor/a |
Notes (Translator) | Combined into the Notes field |
etiquetas |
Notes (Tags) | Combined into the Notes field |
All books are imported with status Available.
Step 1 — dry run (preview what will be imported, no changes written):
cd backend
uv run python import_csv.py /path/to/your_catalogue.csv --dry-runThis prints every line that would be added, skipped as a duplicate, or skipped due to missing data. Review the output before proceeding.
Step 2 — real import:
uv run python import_csv.py /path/to/your_catalogue.csvA summary is printed at the end:
==================================================
Added: 312
Skipped (dup): 4
Skipped (empty): 1
Errors: 0
==================================================
The script checks for duplicates in two ways:
- Against the database — any book already in the DB with the same
title,author,publisher,publishing_date,edition_date, andlanguageis skipped. - Within the CSV — if the same combination of
title,author,publisher,publishing_date,edition_date, andlanguageappears more than once in the file, only the first occurrence is imported.
Running the import a second time on the same file is safe — all rows will be detected as duplicates and skipped.
If the app is running via Docker Compose, copy the CSV into the container and run the script there:
docker compose cp /path/to/your_catalogue.csv backend:/tmp/catalogue.csv
docker compose exec backend uv run python import_csv.py /tmp/catalogue.csv --dry-run
docker compose exec backend uv run python import_csv.py /tmp/catalogue.csv- Click Catalogue in the sidebar, then + Add Book.
- Fill in at least Title and Author. All other fields (publisher, dates, language, notes) are optional.
- For dates, you can enter just a year (
1605), a year and month (2023-05), or a full date (2023-05-15). This is especially useful for classics that have been republished many times — use Original Publication for the first edition year and Publishing Date for the copy you own. - Click Add Book. The book appears in the catalogue.
From the book's detail page you have two options:
- Fetch Cover — Searches Open Library by title and author and downloads the cover automatically. Works best for well-known books.
- Upload — Click the Upload button to pick an image from your computer (JPEG, PNG, or WebP).
This is the fastest way to add a book when you have a physical copy in hand.
- Click Scan in the sidebar.
- Drag and drop (or click to browse) one or more photos of the book. The best shots to use are:
- The front cover — usually contains the title and author.
- The title page (inside front) — often has publisher and year.
- The copyright page (verso of title page) — has edition year, ISBN, and original publication info.
- Click Scan X image(s). Tesseract will extract the text.
- The Review & Save form appears pre-filled with whatever the OCR could identify. Check each field, correct any mistakes, and click Save Book.
- You can expand View raw OCR text to see exactly what was detected if a field looks wrong.
Tip: The cleaner and flatter the photo, the better the results. Good lighting matters more than resolution.
- Open a book's detail page and click Mark Borrowed. The book moves to the Borrowed section.
- To return it, either open the detail page and click Return, or go to Borrowed, find the book, and click the Return button on its card.
- The book's status returns to Available in the catalogue.
Use the Archived status for books stored outside the library (e.g., in boxes in storage).
- Open a book's detail page and click Archive.
- To bring it back, go to Archived and click Restore on the card, or use the Restore button on the detail page.
Every section (Catalogue, Borrowed, Archived) has a search bar that filters by title and author as you type. The search is debounced — just start typing and results update automatically.
Open any book from its card, then:
- Click Edit to modify any field, including status.
- Click Delete → Confirm Delete to permanently remove the book and its cover image.
- Book catalogue — Add, edit, delete books with title, author, publisher, dates, language, and more
- Cover images — Upload covers manually or auto-fetch from Open Library
- Borrowed books — Mark books as borrowed and track them in a dedicated section
- Archived books — Archive books stored outside the library (e.g., in boxes)
- OCR scanning — Upload photos of books to extract metadata via Tesseract OCR
- Search — Filter books by title or author across all sections
- Flexible dates — Supports year-only dates (e.g., "1605") for classics
babel/
├── backend/ # FastAPI + SQLite
│ ├── app/
│ │ ├── main.py # App entry point
│ │ ├── database.py # SQLAlchemy setup
│ │ ├── models.py # Book model
│ │ ├── schemas.py # Pydantic schemas
│ │ ├── routers/ # API endpoints
│ │ └── services/ # OCR + cover services
│ ├── pyproject.toml # Python dependencies (uv)
│ └── Dockerfile
├── frontend/ # SvelteKit
│ ├── src/
│ │ ├── routes/ # Pages
│ │ └── lib/ # Components, API client, types
│ └── Dockerfile
├── docker-compose.yml
└── README.md