RETURN_TO_BASE

PixLume — AI-based Vision Labelling Platform

VISIT_PROJECT

I built PixLume — a full-stack AI platform for labelling computer vision datasets. Upload images, draw bounding boxes, get AI-powered suggestions, review annotations, and export clean datasets. The whole pipeline, zero infrastructure cost.


The Stack

It's a monorepo with npm workspaces:

ComponentTechDeployment
apps/webNext.js 16Vercel
apps/apiFastifyRender
apps/workerBullMQRender (alongside API)
packages/typesShared TypeScript types

Infrastructure — entirely free-tier:

  • Neon — PostgreSQL (Prisma ORM)
  • Upstash — Redis (powers BullMQ job queue)
  • Cloudflare R2 — Image storage (S3-compatible, zero egress fees)
  • Vercel — Frontend hosting
  • Render — API + worker hosting

Zero dollars. The whole thing runs on air.


How It Works

The Flow

You create a project — a container for a dataset. Each project has its own annotation classes (car, person, dog, etc.).

Upload images → they go straight to R2 → the BullMQ worker generates thumbnails in the background. Open the annotation editor — a canvas-based tool built with react-konva — and draw bounding boxes.

AI Suggestions

Instead of drawing every box by hand, hit a button and the platform runs YOLOv2.6 inference on the image. It suggests bounding boxes that you can accept, adjust, or reject.

Review Flow

After annotating, images go through a review pipeline — approve or reject with comments. Critical for team-based annotation with quality control.

Export

Export datasets in standard formats (YOLO, COCO, etc.) as zip files, ready for training.


Security Architecture

The frontend-to-backend communication goes through an SSR proxy — a Next.js catch-all Route Handler that forwards requests server-to-server.

  • JWT tokens live in httpOnly cookies — JavaScript can't touch them. No XSS can steal your session.
  • Token refresh is transparent — if the API returns 401, the proxy automatically refreshes and replays the request.
  • No API URLs leak to the browser — the frontend only talks to /api/* on its own domain.
  • File downloads are proxied — export files stream through the API instead of exposing R2 presigned URLs.

Full CSP header setup: X-Frame-Options: DENY, HSTS with preload, strict connect-src.

Image Proxy

Images in R2 aren't served directly to the browser. An image proxy route on the API fetches from R2 and streams the bytes through — R2 credentials never touch the frontend.

Upload Flow

  1. Presigned URL upload — time-limited signed URL, browser uploads directly to R2
  2. Direct upload — file goes through the API proxy (simpler CORS for smaller files)

Both support batch uploads with automatic thumbnail generation via the BullMQ worker.


The Data Migration Adventure

The original setup ran everything in Docker — PostgreSQL, MinIO, Redis on a single server. Moving to the cloud stack was an experience.

Database: Dumped with pg_dump, but Neon doesn't let you disable foreign key constraints during import. Had to manually reorder all COPY statements in dependency order: users → organizations → memberships → projects → classes → images → annotations.

Images: MinIO stores objects in a custom internal format (xl.meta binary files) — can't just copy files. Ran a migration script that pulled each object through the MinIO API and re-uploaded to R2.


Key Takeaways

  1. Free tier is surprisingly capable. Neon + Upstash + R2 + Vercel + Render gives you a real production stack for $0.
  2. ONNX Runtime is underrated. Replacing a Python ML service with a single JS library call was one of the best decisions.
  3. SSR proxies solve so many security problems. httpOnly cookies + server-side forwarding eliminates entire classes of attacks.
  4. Data migrations are always harder than you think. Especially with foreign keys and proprietary storage formats.
  5. Git LFS for ML models just works. 213MB ONNX file, tracked properly, pulled at build time on Render.