← Back to work Photo SaaS · collaborative curation

Vuelette

A collaborative image curation and delivery tool for photographers and model agencies. Upload shoots, rate and favourite images together, build sets and portfolios with public share links, and let AI surface visually similar images via in-process vector search, with smart versioning that no competitor in the price class offers.

By request · early stage

Vuelette is being grown by request only, with a small set of users, while the core flow is tested before it's opened up to scale. The whole curation pipeline (upload, review, curate, share) is running on real data today; the deliberate, test-first pace is exactly what keeps it solid rather than rushing a public launch and crashing.

~20
Backend route files; 9 database migrations in production.
1536
Matryoshka-truncated embedding dims: top-1 100%, MRR 1.0 on test set.
<200ms
Cosine similarity in plain Node, scaling to ~100k images on SSD.
~$0.02
Embedding cost per 200-image shoot, effectively free in practice.
// What it does

The feature set

Shoot & gallery pipeline CRUD shoots, CLI and web upload (multipart, queued), sharp-based derivative generation (thumb/preview/full), EXIF extraction, and a masonry grid with lazy loading and rich filters.
Visual vector search Gemini multimodal embeddings stored as packed Float32 arrays, with cosine similarity run in-process in Node, with no vector DB. Finds visually similar images cross-shoot. Three benchmarked prototype iterations got it there.
Collaborative curation Ratings, favourites and threaded comments (fully implemented with upsert/toggle/edit/delete), plus sets that span shoots with manual ordering.
Portfolios & share links Public share pages with public / password / expiring access modes, signed URLs, and built-in Umami view statistics per link.
Auth, done right PIN login plus JWT cookies, magic-link via AWS SES, bcrypt and rate limiting. PIN is always a reliable fallback when email is sandboxed.
In-browser healing tool OpenCV.js (WASM) inpainting in the browser with a Python OpenCV backend. A feature no competitor in this price class offers.
// How it's built

The architecture

Backendruntime
Node.js 20+, Fastify 5, better-sqlite3, sharp for imaging, p-queue, exifr, archiver for ZIP delivery, AWS SES for mail.
Frontendclient
React 18 + TypeScript + Vite, Tailwind, TanStack Query, Zustand, React Router. OpenCV.js (WASM) for the in-browser healing tool.
AI / searchintelligence
Gemini Embedding 2 (multimodal visual vectors, Matryoshka-truncated to 1536 dims) plus Gemini 2.5 Flash Vision for auto-tagging. Cosine similarity in pure Node, with no vector-DB dependency.
Datastorage
SQLite in WAL mode, 9 migrations, pre-migration snapshot backups. An image_versions table keeps the same image ID, and its ratings, comments and set memberships, alive when a retouched version is uploaded.
// How it scales

Cheap today. A clear path to scale.

SQLite now, Postgres next, distributed only when it's earned // the cheapest thing that holds at each size
Today
SQLite (WAL)

SQLite in WAL mode is exactly right at this scale: zero infra cost, dead-simple backups, and fast enough that the in-Node vector search returns under 200ms. Running this for almost nothing is a feature, not a shortcut.

Next
→ Postgres

The data layer is built to move to Postgres when concurrent writes and multi-user load make it worth it: the obvious next rung, planned from the start rather than bolted on in a panic.

Then
pgvector

When the image count outgrows in-process cosine, the embeddings move into a real vector index (pgvector); the search stays the same to the user, only the engine underneath changes.

At scale
distributed

A distributed database only when the volume genuinely demands it. Each rung is the cheapest thing that holds at that size; you never pay for big-system infra before you have a big-system problem.

Why it matters

Two ideas competitors
don't have.

The first is visual vector search inside a gallery tool. Three prototype iterations were actually benchmarked and the results saved; the winning approach stores each embedding as a packed Float32 array and runs cosine similarity in plain Node, scaling to ~100k images under 200ms, with no vector-DB dependency, at effectively zero cost per shoot. Pixieset, ShootProof and Pic-Time don't do this.

The second is stable image identity across versions. The image_versions table lets the same image ID, and all of its ratings, comments and set memberships, survive when a photographer uploads a retouched version, matched on the camera's original filename. Competitors treat every upload as a brand-new image; this quietly solves a problem nobody else has.

The honest read: as a personal tool it's ~80 to 85% there; as a distributable SaaS the core works but onboarding, plan limits and billing are the remaining miles. That gap is named on purpose, and so is the data roadmap above: SQLite now because it's the right, cheap choice at this size, Postgres and a vector index when the load earns them. Knowing exactly when to spend is how you avoid shipping slop.

Want a build like this?

Vuelette is what deliberate, test-first building looks like: a genuinely original vector pipeline shipped without the VC theatre. If your product needs the same care, let's compare notes.