Open to new projects
Work / Product I started / PhotoDrop
Case study · Product I started

PhotoDrop.

Event photos in guests' hands the same day. Scan a QR code, see your photos, add your own, download them all.

RoleEngineer, end to end · idea shared with collaborators
ForPhotographers and event guests · Stawi Social Lab
TimelineAug 2026 – ongoing
StackNode, TypeScript, PostgreSQL, Redis, S3, React
StatusLive · Visit ↗
PhotoDrop PhotoDrop on a phone
10,000photos uploaded to one event in a load test
110,000photos in one gallery, still paging fast
1,000guests viewing while 200 upload
+62%video processing throughput after tuning
The problem

After a wedding or a launch, guests wait days for photos. Then they arrive one at a time over WhatsApp, compressed and out of order.

Photographers were juggling Google Drive links, USB sticks and email chains. Guests who took their own photos had no way to add them to the event, and nobody could find the photos from the first dance without scrolling through thousands.

What I built

One QR code per event. Everything else happens behind it.

A photographer creates an event and uploads the whole shoot at once. Guests scan the QR code at the venue and see the gallery in seconds, with no account and no app to install.

  • Bulk uploadThousands of photos in one go, straight to storage with live progress.
  • QR accessEvery event gets a printable QR code. Guests scan and they're in.
  • Guest uploadsGuests add their own photos and short clips until a closing date the owner sets.
  • MomentsBrowse by time, so the first dance is easy to find.
  • Download allOne zip of the whole gallery, built in the background.
  • PrivacyLocation and device data are removed from guest photos before anyone sees them.
The photographer's side: bulk upload, QR codes and galleries
The photographer's side: bulk upload, QR codes and galleries
Architecture

The API never carries a photo.

Uploads go straight from the browser to S3 using signed, multi-part uploads. The API only records what arrived. Resizing, video conversion and zips run on separate workers, so a busy event never slows the gallery down.

Browser / appweb or phone APINode · TypeScript Object storageS3 · photos, zips PostgreSQL Redis queueBullMQ Workersphoto · video · zip upload goes straight to storage
Key decisions

Choices that made it hold up.

  • Guest uploads jump the queue

    Guest photos are processed ahead of the photographer's bulk import, so a guest's picture shows up in seconds even while 10,000 others are waiting.Why: guests leave if they don't see their photo quickly

  • Workers size themselves to the machine

    Each worker reads the CPU and memory it starts on and picks how many photos and videos to process at once.Result: 12-second clips went from 6 to 9.7 per minute

  • Zips stream, never sit in memory

    "Download all" streams photos from storage into a new upload, about 16 MB of memory for a 2 GB zip.Why: a 500-photo zip can't be allowed to crash a worker

  • Counters update once per batch

    Photo and storage counts update once per database statement, not once per row, with a fixed lock order so busy events never deadlock.Result: a 20,000-row import went from about a minute to a few seconds

  • Nothing gets stuck

    Every five minutes the worker re-queues anything stuck in "uploaded". Deleted photos stay recoverable for 30 days, then are purged.Why: a Redis blip should never lose someone's photo

  • Two kinds of access

    Photographers sign in with rotating tokens. Guests get a scoped access token from the QR code that only opens that one event.Why: no guest accounts, and no guest can see another event

Across the lifecycle

What each stage looked like on this project.

  • 01IdeaOne problem: guests waiting days for event photos. First release scoped to scan, view, download.
  • 02DesignDirect-to-storage uploads, a job queue and separate workers designed before any code.
  • 03BuildTypeScript API and workers, raw SQL with 14 numbered migrations, React front end.
  • 04TestEnd-to-end security checks on guest uploads, privacy, blocking and zips.
  • 05DeployDocumented deployment, Docker stacks for development and testing, S3 bucket policies written down.
  • 06SecureScoped guest tokens, rate limits, metadata stripped from guest photos, guest blocking.
  • 07ScaleLoad, crowd and resilience tests against a throwaway stack.
  • 08MaintainAutomatic recovery of stuck jobs, 30-day trash, zip expiry, counter recalculation.
Evidence

Every scenario runs against a disposable copy of the system.

CheckWhat it doesResult
owner10kOne photographer uploads 10,000 photosGallery and downloads hold at 10k
read100kGallery reads with 110,000 photos in one eventCursor paging stays fast
guest1000One guest uploads 1,000 × 12 MP photos, then a 500-photo zipProcessed and zipped
crowd200 guests uploading and 1,000 viewing at onceGuest uploads stay first in line
resilienceKill the worker, kill the API, restart Redis, freeze PostgresRecovers without losing photos
bulkBulk inserts with concurrent writersNo deadlocks · minutes to seconds
capacityPhoto and clip throughput at different worker settingsVideo +62%
$ npm run loadtest:up          # start the throwaway stack
$ npm run loadtest:owner10k    # 10,000 photos, gallery and downloads
$ npm run loadtest:crowd       # 200 uploading + 1,000 viewing
$ npm run loadtest:resilience  # break things on purpose
$ npm run loadtest:down        # remove the stack and its data
What's next

Find every photo you're in. Face matching is designed and planned for the next phase: guests take one selfie and get back every photo they appear in.

Contact

Have an idea or a system to build?

Tell me what you need. I'll reply within a day with questions, a rough approach, or a time to talk.

Phone+254745600377
Based inNairobi, Kenya · working worldwide
What do you need?