Real-time location sharing โ privacy-first, built to scale.
MeetUp is a full-stack mobile + backend platform that lets two people share their live location with each other and automatically detect when they've met. It combines a FastAPI WebSocket backend with a React Native mobile client, using Redis pub/sub for multi-instance broadcasting and PostgreSQL with PostGIS for persistent geospatial data.
- What It Does
- Tech Stack
- Project Structure
- Architecture Overview
- Key Features
- WebSocket Protocol
- REST API Endpoints
- Getting Started
- Testing
- Code Quality
- Deployment
- Additional Documentation
MeetUp solves a simple but surprisingly hard problem: "I'm on my way โ where are you?"
Instead of sending a static pin, both users share live GPS location in a private session. The app:
- Shows real-time positions on a map as each person moves
- Calculates live distance between the two users
- Detects proximity (โค 50m) and triggers an "I'm Here" confirmation flow
- Automatically ends the session and celebrates once both confirm they've met
The session is ephemeral โ no location history is stored after it ends. Privacy is built in by design.
| Layer | Technology |
|---|---|
| Mobile | React Native (Expo), React Navigation |
| Backend API | Python 3.11, FastAPI |
| Realtime | WebSockets (FastAPI native), Redis Pub/Sub |
| Database | PostgreSQL 15 + PostGIS extension |
| Cache / Broker | Redis |
| Auth | Supabase JWT (verified server-side via pyjwt) |
| Migrations | Alembic |
| Containerisation | Docker + Docker Compose |
| Linting | Ruff (Python), ESLint + Prettier (JS) |
| Testing | Pytest (backend), manual E2E (mobile) |
MeetUp/
โโโ backend/ # FastAPI service
โ โโโ app/
โ โ โโโ api/ # REST route handlers
โ โ โ โโโ endpoints/ # users, sessions, requests, metrics, realtime
โ โ โโโ core/ # Config, database, auth helpers
โ โ โโโ models/ # SQLAlchemy ORM models
โ โ โโโ realtime/ # WebSocket gateway & ConnectionManager
โ โ โโโ worker/ # Background workers (e.g. session cleanup)
โ โโโ alembic/ # Database migration scripts
โ โโโ tests/ # Pytest test suite
โ โโโ seed.py # Creates test users + session for local dev
โ โโโ Dockerfile
โ โโโ requirements.txt
โ
โโโ mobile/ # React Native (Expo) app
โ โโโ src/
โ โโโ screens/ # All app screens (Home, Session, Auth, etc.)
โ โโโ components/ # Reusable UI components
โ โโโ services/ # locationService, realtimeService, analyticsService
โ โโโ context/ # AuthContext (JWT, deep-link handling)
โ โโโ navigation/ # AppNavigator (tab + stack navigation)
โ โโโ api/ # HTTP API client wrappers
โ
โโโ web/
โ โโโ client.html # Standalone WebSocket debug client
โ
โโโ scripts/ # lint.sh, setup_hooks.sh
โโโ docker-compose.yml
โโโ ARCHITECTURE.md # Deep-dive into multi-instance design
โโโ PROTOCOL.md # WebSocket message specification
โโโ QUICK_START.md # Setup for new contributors
The backend is designed for horizontal scaling from day one. Multiple API instances run behind a load balancer; they never share in-process memory. All cross-instance communication goes through Redis.
Mobile / Web clients
โ
HTTP + WebSocket
โ
โโโโโโโโโโโดโโโโโโโโโโ
โ Load Balancer โ (nginx / cloud LB)
โโโโโโโโโโโฌโโโโโโโโโโ
โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ โ โ
API-1 API-2 API-N โ Stateless FastAPI instances
โ โ โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ
โโโโโโโผโโโโโโ
โ Redis โ โ Pub/Sub (session:*), rate-limit keys, presence TTLs
โโโโโโโฌโโโโโโ
โ
โโโโโโโผโโโโโโโโโโโ
โ PostgreSQL โ โ Persistent storage (users, sessions, requests, audit)
โ + PostGIS โ
โโโโโโโโโโโโโโโโโโ
When User A (connected to API-1) sends a location update:
- API-1 validates the message (coordinates, accuracy, timestamp, jump detection)
- API-1 publishes the event to
session:{uuid}on Redis - All API instances subscribed to that channel receive it
- Each instance forwards the payload to any local WebSocket connections in that session
- User B (connected to API-2) receives the location update in real time
Full design docs โ ARCHITECTURE.md
- JWT-based auth, issued by Supabase, verified server-side on every request and WebSocket upgrade
- User registration and login handled on the mobile client; backend is stateless
- Users can search friends and send a meet request
- Deep-link support:
meetup://request/{requestId}opens the request inbox directly - Accept/reject flow with inline status feedback and WhatsApp-friendly invite sharing
- Both users join a shared session by ID (or via deep-link
meetup://session/{sessionId}) - Location updates stream over WebSocket at up to 10 updates/sec per user (rate-limited server-side)
- Server validates every location packet:
- Coordinates within valid bounds (lat: ยฑ90, lon: ยฑ180)
- Accuracy between 0.1m โ 100m
- Timestamp within ยฑ5 minutes of server time
- Speed check: rejects jumps > 300 km/h (impossible movement detection)
- Live distance bar shows current separation
- Status changes as users approach: Far away โ Getting close โ Nearly there
- "I'm Here" CTA unlocks at โค 50m with a pulse animation
- User taps "I'm Here" โ 60-second confirmation timer starts
- Both users must confirm within the window
- Session auto-ends; a celebration animation plays
- Fallback: manual confirm path if timer edge-cases occur
- Pause Sharing: stops broadcasting GPS while keeping the session alive
- App automatically pauses on background and resumes on foreground
- Stale/expired peer location states shown clearly in UI (TTL-aware)
- Grace-window countdown before exponential retry kicks in
- Status badge updates through grace โ reconnect โ live states
- Snapshot re-sync automatically on reconnect
- URL continuity: retries always use the original backend base URL
GET /api/v1/metricsreturns per-instance counters and gauges (WS connections, messages, rate-limit hits, validation errors)- Alerting thresholds documented in ARCHITECTURE.md
Connection URL
ws://<host>/api/v1/ws/meetup?token=<JWT>&session_id=<UUID>
| Message | Description |
|---|---|
location_update |
Broadcast current GPS position |
end_session |
Manually close the session |
// location_update
{
"type": "location_update",
"payload": {
"lat": 12.9716,
"lon": 77.5946,
"accuracy_m": 5.0,
"timestamp": "2026-04-10T09:00:00Z"
}
}| Message | Description |
|---|---|
peer_location |
Another participant's updated position |
presence_update |
A user joined or left the session |
session_ended |
Session closed (user action or proximity) |
error |
Validation failure, rate limit, etc. |
// error example
{
"type": "error",
"payload": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many updates"
}
}Full payload schemas โ PROTOCOL.md
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/users/register |
Register a new user |
GET |
/api/v1/users/{id} |
Fetch user profile |
POST |
/api/v1/requests |
Send a meet request |
GET |
/api/v1/requests |
List incoming / outgoing requests |
POST |
/api/v1/requests/{id}/accept |
Accept a meet request |
POST |
/api/v1/sessions |
Create a new session |
GET |
/api/v1/sessions/{id} |
Get session details |
DELETE |
/api/v1/sessions/{id} |
End a session |
POST |
/api/v1/sessions/{id}/im-here |
Confirm proximity ("I'm Here") |
POST |
/api/v1/invite/redeem |
Redeem a deep-link invite token |
GET |
/api/v1/metrics |
Per-instance observability counters |
WS |
/api/v1/ws/meetup |
Realtime location WebSocket |
- Docker & Docker Compose
- Node.js โฅ 18 (for mobile development)
- A Supabase project (for JWT secret)
cp .env.example .envOpen .env and fill in:
SUPABASE_URLโ your Supabase project URLSUPABASE_KEYโ your Supabase JWT Secret (used for server-side token verification)
docker-compose up -d --buildThis starts PostgreSQL (with PostGIS), Redis, and the FastAPI backend.
# Apply DB schema (first run only)
docker-compose exec backend alembic upgrade head
# Create test users (Alice & Bob) + an active session
docker-compose exec backend python seed.pySave the output! It contains the Session ID and JWT tokens for Alice and Bob โ you'll need these to test the WebSocket.
- Open
web/client.htmlin your browser (no server needed โ it's a plain HTML file) - Paste Alice's JWT token and the Session ID
- Click Connect
- Open a second tab for Bob and do the same
- Move Alice's location โ Bob's tab should update in real time
cd mobile
npm install
npx expo startScan the QR code with Expo Go or run on a simulator.
# All tests
docker-compose exec backend pytest
# With output
docker-compose exec backend pytest -v
# Specific file
docker-compose exec backend pytest tests/test_realtime.pypython quick_validation_test.pyRuns a lightweight connectivity check without Docker.
./scripts/lint.shdocker-compose exec backend ruff check .
docker-compose exec backend ruff format .cd mobile
npm run lint # check
npm run lint:fix # auto-fix
npm run format # prettier./scripts/setup_hooks.shInstalls git hooks that automatically lint and format on every commit:
- โ Python (Ruff check + format)
- โ JavaScript (ESLint + Prettier)
- โ Trailing whitespace & large file checks
Manual run:
pre-commit run --all-filesSkip (not recommended):
git commit --no-verifyThe system is designed for multi-instance deployment. A reference docker-compose.yml runs 3 backend instances behind nginx for local load-balancer testing:
# Scale to N instances (with external nginx)
docker-compose up -d --scale backend=3Each instance is fully stateless โ scale up or down without downtime. Redis handles all cross-instance coordination.
Scalability estimates (per instance, 4 CPU / 8 GB RAM):
| Metric | Limit |
|---|---|
| WebSocket connections | ~1,000 |
| Concurrent sessions | ~500 |
| Location updates / sec | ~5,000 |
| Broadcast latency | < 50ms |
| Document | Contents |
|---|---|
| ARCHITECTURE.md | Full multi-instance design, failure modes, scaling strategy |
| PROTOCOL.md | Complete WebSocket message specification |
| QUICK_START.md | Condensed onboarding for new contributors |
| FRONTEND_PROGRESS_README.md | Week-by-week mobile feature tracker |
Private project. All rights reserved.