Skip to main content

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:

  1. Do the simplest thing that works
  2. Fail fast, fail loud
  3. Zero clever wrappers
  4. Data > code (store rules in JSON, not code)
  5. 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 contents
  • GET /api/item?path=<path> - Get item metadata
  • PUT /api/item?path=<path> - Update item metadata
  • GET /api/thumb?path=<path> - Get/generate thumbnail
  • GET /api/search?q=<query> - Search items
  • GET /api/health - System health

Contributing

  1. Follow the dev guide principles in dev_notes/
  2. Keep PRs < 400 lines
  3. Test with sample data
  4. Maintain performance budgets
  5. No clever abstractions

Publishing

cd /home/ubuntu/dev/lenslet
pip install build twine
python -m build
twine upload dist/*

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lenslet-0.1.0.tar.gz (378.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lenslet-0.1.0-py3-none-any.whl (96.9 kB view details)

Uploaded Python 3

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

Hashes for lenslet-0.1.0.tar.gz
Algorithm Hash digest
SHA256 edcca0d85ea5fa70d96ea28851180ee089c77553156bfda348ecd38a3c6b44c9
MD5 098a1dc06dc2ff06cb9032925596b30a
BLAKE2b-256 723c8cd24e5f1b55bf85f598ce7a2834f56aae5080232ca0d04a525853f780b9

See more details on using hashes here.

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

Hashes for lenslet-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 92dec88d8c676453d8ea1d1a593bc04f54594585065a546134b6ac7bcb0269c1
MD5 8b44abcb11d6701a7bd845f0f4c14a4f
BLAKE2b-256 10251add09d15594f94ba11879e69a13ddfa5a033ca558c3ed3a347e0633e665

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page