https://github.com/engnadeau/books
Nick's personal reading library — Goodreads replacement backed by a single books.json and an Astro site.
https://github.com/engnadeau/books
astro books goodreads open-library reading static-site
Last synced: about 1 month ago
JSON representation
Nick's personal reading library — Goodreads replacement backed by a single books.json and an Astro site.
- Host: GitHub
- URL: https://github.com/engnadeau/books
- Owner: engnadeau
- License: mit
- Created: 2026-04-16T17:33:12.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-18T02:47:22.000Z (2 months ago)
- Last Synced: 2026-05-18T04:52:13.057Z (2 months ago)
- Topics: astro, books, goodreads, open-library, reading, static-site
- Language: TypeScript
- Homepage: https://engnadeau.github.io/books/
- Size: 67.4 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# books
A self-hosted, version-controlled replacement for Goodreads. Books live as a
single JSON file in git; an Astro static site renders the ones I've read.
- **Live site:** https://engnadeau.github.io/books/
- **Canonical data (for LLMs / external tools):**
https://raw.githubusercontent.com/engnadeau/books/main/books.json
## Why
Goodreads' data model is noisy and its export format is hostile. A flat JSON
file in a public git repo is easier to edit, easier to fetch, and trivial for
a language-model agent to reason over via the raw GitHub URL.
## Local development
```sh
pnpm install
pnpm dev
```
Open http://localhost:4321.
## Scripts
```sh
# One-shot seed: turn a Goodreads CSV export into books.json.
# Refuses to overwrite an existing books.json unless --force is passed.
pnpm import "/path/to/goodreads_library_export.csv"
# Fetch cover URLs from Open Library. Tries ISBN lookup first,
# falls back to title+author search. Writes URLs to each book's cover_url field.
# --only-read limits to read books; --force re-fetches books that already have covers.
pnpm covers --only-read
# Build/preview.
pnpm build
pnpm preview
```
## Schema
`books.json` is a JSON array of:
```ts
{
id: string; // stable slug, never regenerated
title: string;
author: string;
series: { name: string; position: number } | null;
isbn: string | null; // ISBN13 preferred, else ISBN10
cover_url: string | null; // Open Library URL, populated by pnpm covers
status: "read" | "reading" | "to-read" | "dnf";
rating: 1 | 2 | 3 | 4 | 5 | null;
date_started: string | null; // YYYY-MM-DD
date_finished: string | null; // YYYY-MM-DD
tags: string[]; // lowercase kebab-case
}
```
The Zod schema in `src/content.config.ts` validates this on every `astro sync`
and build, so malformed entries fail loudly instead of silently breaking the
site.
## Deployment
GitHub Pages via `.github/workflows/deploy.yml`. Every push to `main` rebuilds
and redeploys. The workflow uses `withastro/action@v6`, which auto-detects
pnpm from the lockfile.
## Project layout
```
books.json source of truth
src/content.config.ts Zod schema + file loader
src/pages/index.astro only page; filters to status === "read"
src/components/BookTable.astro cover grid + client-side search/sort
src/styles/global.css
scripts/import-goodreads.ts CSV → books.json
scripts/fetch-covers.ts books.json → books.json (with cover_url)
docs/superpowers/specs/ design docs
```
## Design intent
- `id` is immutable once created. Everything else can be hand-edited.
- `books.json` is written with `JSON.stringify(x, null, 2) + "\n"` so git
diffs stay clean.
- A future MCP server will read and write `books.json` directly — no new
schema, no transformation layer.