Local Photo Browser
Index a photo folder onto a USB drive, then browse it in an Apple-Photos-style grid that runs entirely in the browser.
- 01Indexer and viewer, no server
- 02Built-in SQLite, no build step
- 03Resumable and content-addressed
- 04Tagging is optional and local
- 05Robust scan
- 06Viewer that scales
Architecture Overview
A two-part photo browser that never touches the internet at runtime. A Node.js CLI scans a source folder, reads EXIF, builds WebP thumbnails, optionally captions and tags each image with a local Ollama vision model, and writes everything to a portable .photoapp/ folder — SQLite plus thumbnails — on a USB drive. A dependency-free static viewer copied to the drive root reads that SQLite file in the browser through sql.js and gives an Apple-Photos-style grid with filters, sorting and a lightbox. Everything on the drive uses relative paths, so it works mounted at any drive letter.
How it works
Core mechanics, failure recovery paths, and system design decisions.
Indexer and viewer, no server
The CLI produces the data; the viewer is HTML, CSS, JS and a sql.js WASM blob. The viewer makes zero network requests after load — queries run locally in WASM — and works from file:// or a bundled zero-dependency serve.js built on Node stdlib alone.
Built-in SQLite, no build step
Storage is the node:sqlite module, so there is no native compile: sharp ships prebuilt binaries and heic-convert is a pure-JS HEIF fallback. Requires Node 22.5 or newer.
Resumable and content-addressed
Every file is committed to SQLite before the next starts, so Ctrl+C and re-run is safe. Unchanged files are skipped on path plus mtime plus size without re-hashing; moved or duplicated content is matched on SHA-256 and reuses the existing thumbnails and row.
Tagging is optional and local
With a model configured, each image gets a caption and tags from Ollama over localhost; --skip-tagging does EXIF and thumbnails only, which is the recommended first pass. The only step that ever needs the internet is the one-time npm install.
Robust scan
Corrupt files are logged to a scan_errors table and skipped rather than crashing the batch. Formats cover JPEG, PNG, HEIC, WebP, TIFF and RAW, with RAW decoded from its embedded preview.
Viewer that scales
A windowed grid stays smooth past 50k items, with date-range, format and tag filters read from the database, sortable by date taken, date added or filename, and a lightbox showing the 1200px preview with caption, tags and EXIF.
Engineering Highlights
- •No backend anywhere — the viewer runs from file:// and queries SQLite in WASM
- •SQLite is the built-in node:sqlite module, so there is no native build step
- •Indexing is resumable and deduplicates moved or copied photos on SHA-256
- •AI captions and tags are optional and come from a local Ollama model over localhost
- •Everything on the USB uses relative paths, so it works at any mount point
- •The only online step is the one-time npm install; nothing else touches the network