Lightweight image gallery server - no database, no files left behind
Project description
Lenslet Gallery System
A minimal, fast, boring (on purpose) gallery system with React frontend and FastAPI backend. Built for performance and simplicity.
Starting the project
in frontend folder, run the following commands to build the frontend:
npm run build
in backend folder, run the following commands:
uvicorn src.lenscat_backend.main:app --reload --host 127.0.0.1 --port 7070
Architecture
- Frontend: React + TanStack Query/Virtual + minimal CSS
- Backend: FastAPI + flat file storage (local/S3) + async workers
- Storage: No database - JSON manifests + sidecars + thumbnails
Quick Start
1. Backend Setup
cd lenscat-backend
# Install dependencies
pip install -r requirements.txt
# Create sample data (optional)
python scripts/create_sample_data.py
# Start backend
python scripts/dev.py
Backend runs at http://localhost:8000 with API docs at /docs.
2. Frontend Setup
cd lenscat-lite
# Install dependencies
npm install
# Set API endpoint
echo "VITE_API_BASE=http://localhost:8000/api" > .env.local
# Start frontend
npm run dev
Frontend runs at http://localhost:5173.
Project Structure
lenslet/
├── lenscat-backend/ # FastAPI backend
│ ├── src/
│ │ ├── api/ # API endpoints
│ │ ├── models/ # Pydantic models
│ │ ├── storage/ # Storage backends (local/S3)
│ │ ├── workers/ # Indexing & thumbnail workers
│ │ ├── utils/ # Utilities (EXIF, hashing, thumbs)
│ │ └── main.py # FastAPI app
│ ├── scripts/ # Development scripts
│ ├── data/ # Local storage (dev)
│ └── requirements.txt
│
├── lenscat-lite/ # React frontend
│ ├── src/
│ │ ├── api/ # API client & query hooks
│ │ ├── components/ # React components
│ │ ├── hooks/ # Custom hooks
│ │ ├── lib/ # Utilities & types
│ │ ├── App.tsx # Main app
│ │ └── main.tsx # Entry point
│ ├── package.json
│ └── vite.config.ts
│
└── dev_notes/ # Design documents
├── Developer_note.md # Core dev principles
├── PRD_v0.md # Frontend PRD
└── PRD_v0_backend.md # Backend PRD
Key Features
Backend
- Dual storage: Local filesystem or S3 with unified API
- Smart indexing: On-demand folder manifest building
- Fast thumbnails: pyvips + WebP generation
- Simple search: Full-text across filenames, tags, notes
- Flat files: No database, JSON manifests + sidecars
- Performance: Async workers, caching, BLAKE3 hashing
Frontend
- Virtualized grid: Smooth scrolling for thousands of images
- Eagle-inspired theme: Dark, minimal, performance-first
- Real-time metadata: Tags/notes save to sidecars immediately
- Keyboard navigation: Arrow keys, Enter, shortcuts
- Responsive: Works on desktop and mobile
- No bloat: No global state, UI kits, or CSS-in-JS
Development Philosophy
Following the "minimal, fast, boring (on purpose)" principles:
- Do the simplest thing that works
- Fail fast, fail loud
- Zero clever wrappers
- Data > code (store rules in JSON, not code)
- Sidecar is the source (metadata lives next to images)
File Organization
The system expects this structure:
data/
├── _index.json # Root folder manifest
├── _rollup.json # Search index
├── image1.jpg # Image file
├── image1.jpg.json # Sidecar metadata
├── image1.jpg.thumbnail # WebP thumbnail
└── subfolder/
├── _index.json # Subfolder manifest
└── ...
Configuration
Backend (.env)
STORAGE_TYPE=local # or s3
LOCAL_ROOT=./data
S3_BUCKET=my-gallery
S3_PREFIX=gallery/
HOST=0.0.0.0
PORT=8000
Frontend (.env.local)
VITE_API_BASE=http://localhost:8000/api
Performance Targets
- Time to first grid: < 700ms hot, < 2s cold
- Scroll performance: < 1.5% dropped frames
- Inspector open: < 150ms
- Thumbnail cache hit: > 85%
- Indexing throughput: > 300 items/sec
Deployment
Docker Backend
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src/ ./src/
EXPOSE 8000
CMD ["python", "-m", "src.main"]
Static Frontend
npm run build
# Deploy dist/ to CDN/static hosting
API Endpoints
GET /api/folders?path=<path>- List folder contentsGET /api/item?path=<path>- Get item metadataPUT /api/item?path=<path>- Update item metadataGET /api/thumb?path=<path>- Get/generate thumbnailGET /api/search?q=<query>- Search itemsGET /api/health- System health
Contributing
- Follow the dev guide principles in
dev_notes/ - Keep PRs < 400 lines
- Test with sample data
- Maintain performance budgets
- No clever abstractions
Publishing
cd /home/ubuntu/dev/lenslet
pip install build twine
python -m build
twine upload dist/*
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lenslet-0.1.0.tar.gz.
File metadata
- Download URL: lenslet-0.1.0.tar.gz
- Upload date:
- Size: 378.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
edcca0d85ea5fa70d96ea28851180ee089c77553156bfda348ecd38a3c6b44c9
|
|
| MD5 |
098a1dc06dc2ff06cb9032925596b30a
|
|
| BLAKE2b-256 |
723c8cd24e5f1b55bf85f598ce7a2834f56aae5080232ca0d04a525853f780b9
|
File details
Details for the file lenslet-0.1.0-py3-none-any.whl.
File metadata
- Download URL: lenslet-0.1.0-py3-none-any.whl
- Upload date:
- Size: 96.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92dec88d8c676453d8ea1d1a593bc04f54594585065a546134b6ac7bcb0269c1
|
|
| MD5 |
8b44abcb11d6701a7bd845f0f4c14a4f
|
|
| BLAKE2b-256 |
10251add09d15594f94ba11879e69a13ddfa5a033ca558c3ed3a347e0633e665
|