Event-centric photo sharing β no app required for guests.
Hosts create an event, share a QR code, and guests upload photos instantly from their phone browser. No sign-up, no friction. Photos auto-expire after the event unless saved to a personal vault.
After every event β weddings, birthdays, team offsites β photos end up scattered across a dozen different phones. The host spends days chasing people on WhatsApp for photos that never arrive.
Vibely fixes this in 3 steps:
- Host creates an event and shares a QR code
- Guests scan and upload directly from their browser β no account needed
- Everyone sees the full gallery in real time
| Feature | Description |
|---|---|
| Guest Uploads | Guests upload photos without creating an account β just scan the QR and enter a name |
| Event Gallery | Real-time photo grid with lightbox preview, hosted on ImageKit CDN |
| Personal Vault | Save favourites across all events before photos expire |
| Photo Detail | Full metadata view β uploader, date, event, download original |
| Event Management | Create, edit, cover image upload, QR code sharing, member management |
| User Profiles | Name, bio, avatar upload, upload stats |
| Auto-Expiry | Photos auto-expire with the event; saved vault photos persist |
| Rate Limiting | Upload and session abuse prevention via Upstash Redis |
| Tool | Purpose |
|---|---|
| Turborepo | Monorepo build system with pipeline caching |
| pnpm workspaces | Package management across apps |
| Tool | Purpose |
|---|---|
| Next.js 14 | App Router, Server Components, API Routes |
| Tailwind CSS | Utility-first styling |
| TypeScript | Strict mode throughout |
| Tool | Purpose |
|---|---|
| Expo SDK 51 | React Native with managed workflow |
| NativeWind | Tailwind for React Native |
| React Navigation | Stack + bottom tab navigation |
| EAS Build | Cloud builds for iOS & Android |
| Tool | Purpose |
|---|---|
| Supabase | PostgreSQL, Auth, Storage, Edge Functions, pg_cron |
| ImageKit | CDN image delivery with real-time transformations |
| Upstash Redis | Serverless rate limiting |
| Vercel | Web deployment |
- Entity types and Zod validation schemas
- Storage key utilities and ImageKit URL builders
- Invite token generation (nanoid)
- API constants and error codes
vibely/
βββ apps/
β βββ web/ # Next.js 14 web app
β β βββ app/ # App Router pages
β β β βββ api/ # 20+ API route handlers
β β β βββ dashboard/ # Event list
β β β βββ events/[id]/ # Event detail + edit
β β β βββ photos/[id]/ # Photo detail
β β β βββ vault/ # Personal vault
β β β βββ profile/ # User profile
β β β βββ guest/[token]/ # Guest upload page
β β βββ components/ # Shared UI components
β β βββ hooks/ # useEvents, usePhotos, useVault, useProfile
β β βββ lib/ # Supabase client, rate limiter
β β βββ middleware.ts # Auth guard
β β
β βββ mobile/ # Expo React Native app
β βββ screens/ # All screen components
β βββ components/ # Shared RN components
β βββ hooks/ # Mobile-specific hooks
β βββ navigation/ # Stack + tab navigators
β βββ lib/ # Supabase client, AsyncStorage
β
βββ packages/
β βββ shared/ # Shared TypeScript package
β βββ types/ # Entity type definitions
β βββ validation/ # Zod schemas
β βββ utils/ # storage, invite helpers
β
βββ supabase/
βββ migrations/ # 005 migration files
βββ functions/ # Edge Functions
- Node.js 20+
- pnpm 9+
- Supabase CLI
- Expo CLI (for mobile)
git clone https://github.com/Suharshit/vibely.git
cd vibely
pnpm install# Start local Supabase
supabase start
# Run all migrations
supabase db push# Web
cp apps/web/.env.example apps/web/.env.local
# Mobile
cp apps/mobile/.env.example apps/mobile/.envFill in the values β see .env.example files for documentation on each variable.
Required services:
- Supabase β database, auth, storage
- ImageKit β image CDN (free tier: 20GB/month)
- Upstash β Redis for rate limiting (free tier: 10k commands/day)
# Run all apps in parallel
pnpm dev
# Or individually
pnpm --filter web dev # http://localhost:3000
pnpm --filter mobile start # Expo dev serverpnpm status # custom health check scriptThe Supabase PostgreSQL schema consists of 7 core tables:
users β auth profile (name, email, avatar, bio)
events β event metadata (title, dates, invite_token, cover_image)
event_members β user β event membership with role (host/contributor/viewer)
photos β photo metadata (storage_key, status, uploader references)
personal_vault β user β photo many-to-many for saved photos
guest_sessions β session tokens for accountless guest uploads
Row Level Security (RLS) is enabled on all tables. Key policies:
- Users can only read events they're members of
- Only hosts can edit or delete events
- Photos are only visible to event members
- Guest sessions are managed via service role only
Vibely uses a two-step signed URL upload flow to avoid routing file bytes through the Next.js server:
Client API (Next.js) Supabase Storage
β β β
βββ POST /api/photos/upload ββ β
β (filename, type, size) β
β βββ signed URL + photo_id β
β β
ββββββββββββββ PUT signed URL ββββββββββββββββββββββββ
β (raw file bytes) stores file
β
βββ POST /api/photos/:id/complete ββ
β verifies file exists in storage
β status: 'uploading' β 'active'
βββββββββββββββββ activated photo ββββββββββββββββββ
Benefits: no server body size limits, native upload progress tracking, lower latency.
Four pg_cron jobs run on schedule inside Postgres:
| Job | Schedule | What it does |
|---|---|---|
expire-events |
Daily 00:05 UTC | Marks events as expired when expires_at passes |
soft-delete-expired-photos |
Daily 00:15 UTC | Marks un-saved photos as deleted after event expiry |
cleanup-abandoned-uploads |
Hourly :30 | Removes uploading rows older than 2 hours |
cleanup-old-guest-sessions |
Daily 01:00 UTC | Removes guest sessions older than 90 days |
A Supabase Edge Function runs daily at 02:00 UTC to hard-delete storage files for photos soft-deleted 7+ days ago.
Implemented via Upstash Redis with a sliding window counter:
| Endpoint | Limit |
|---|---|
POST /api/photos/upload |
20 uploads / user / hour |
POST /api/guest/session |
5 sessions / IP / 15 min |
POST /api/events/:id/join |
10 attempts / IP / minute |
| Auth endpoints | 10 attempts / IP / 15 min |
Rate limiting degrades gracefully β if Upstash is unavailable, requests are allowed through.
The Expo app supports all MVP features with platform-native implementations:
expo-image-picker+expo-image-manipulatorfor photo selection and compression (images compressed to max 2400px / 82% JPEG before upload)expo-file-systemuploadAsyncfor upload progress tracking (React Nativefetchdoesn't support upload progress)expo-media-libraryfor saving photos to camera rollAsyncStoragefor guest session persistence- Bottom tab navigation: Events / Vault / Profile
- Action sheets (iOS) and Alert dialogs (Android) for destructive actions
EAS Build profiles:
eas build --profile preview # internal testing
eas build --profile production # App Store / Play Store# Connect repo to Vercel, set root directory to apps/web
# Add all environment variables from apps/web/.env.example
vercel --prodVerify deployment: https://yourdomain.vercel.app/health
cd apps/mobile
eas init # initialize EAS project
eas build --platform all --profile production
eas submit --platform all # submit to storesContributions are welcome. Please read the contributing guidelines and open an issue before submitting a PR for large changes.
# Development workflow
git checkout -b feat/your-feature
pnpm lint
pnpm typecheck
pnpm test
git push origin feat/your-feature
# Open a PR against developMIT β see LICENSE for details.