💬 No sign-up. No followers. Just people near you, talking right now.
Yapper is a full-stack, real-time chat app where rooms are tied to a physical location instead of a topic or a friend list. You only see rooms created near you, and they disappear on their own after a couple of hours — so it's less "social network" and more "who's around right now."
- 📍 Finds your location — the browser's Geolocation API grabs your coordinates on load (with your permission).
- 🗺️ Shows nearby rooms — the backend calculates the distance from you to every active room using the Haversine formula and only returns ones within a 5 km radius.
- ➕ Create a room — spin up a new room at your current location in a couple of taps; no account needed.
- ⏳ Rooms self-destruct — every room carries a
created_at/expires_atpair and auto-expires 2 hours after creation, keeping the map fresh. - ⚡ Chat in real time — once inside a room, messages flow over a WebSocket connection, broadcast instantly to everyone else in that room.
- 👤 Anonymous by design — no login. A random
client_idis generated and persisted inlocalStorage, paired with whatever nickname you type in — that's your identity for the session. - 🚫 One room at a time — the backend's connection manager tracks which client is in which room and rejects a second simultaneous room connection from the same client.
- 🕓 Message history — join a room and instantly get replayed the existing conversation, pulled from the database over the same socket.
yapper/
├── backend/ # 🐍 FastAPI application
│ ├── requirements.txt
│ └── app/
│ ├── main.py # 🚀 app entrypoint — routes + WebSocket wiring
│ ├── config.py # ⚙️ env-driven settings (DB url, radius, TTL)
│ ├── database.py # 🗄️ SQLModel engine + session setup
│ │
│ ├── api/v1/
│ │ ├── rooms.py # POST /rooms, GET /rooms/nearby
│ │ └── messages.py # GET /rooms/{id}/messages (history)
│ │
│ ├── core/
│ │ └── geo.py # 📐 Haversine distance + radius check
│ │
│ ├── models/ # 🧱 SQLModel tables
│ │ ├── room.py # Room (location, TTL, active flag)
│ │ └── message.py # Message (room_id, sender, content)
│ │
│ ├── schemas/ # 📦 Pydantic request/response shapes
│ │ ├── room.py
│ │ └── message.py
│ │
│ ├── services/ # 🧠 business logic, decoupled from routes
│ │ ├── room_service.py # create/find nearby rooms, expire stale ones
│ │ └── message_service.py # persist + fetch messages
│ │
│ └── websocket/
│ ├── connection_manager.py # tracks room ↔ socket ↔ client mappings
│ └── handlers.py # join / broadcast / disconnect logic
│
└── frontend/ # ⚛️ React application
└── src/
├── App.jsx # 🧠 top-level state, geolocation, WebSocket client
├── index.css # 🎨 Tailwind entrypoint
└── components/
├── LandingPage.jsx # nickname entry screen
├── Sidebar.jsx # app shell / navigation
├── RoomList.jsx # nearby rooms list
├── CreateRoomModal.jsx # "create a room here" form
└── ChatArea.jsx # message list + composer
| Layer | Tech |
|---|---|
| Backend framework | FastAPI (async Python) |
| Real-time layer | native WebSockets (FastAPI/Starlette) |
| ORM / models | SQLModel (SQLAlchemy + Pydantic) over SQLite |
| Config | pydantic-settings + .env |
| Frontend | React 19, Tailwind CSS, lucide-react icons |
| Location logic | Browser Geolocation API + server-side Haversine formula |
| Server | Uvicorn |
- 🐍 Python 3.11+
- 🟢 Node.js 18+ and npm
- A browser that supports the Geolocation API (all modern browsers)
1️⃣ Backend
cd backend
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
uvicorn app.main:app --reload # runs on http://127.0.0.1:80002️⃣ Frontend
cd frontend
npm install
npm start # runs on http://localhost:3000Open the app, allow location access, pick a nickname, and either join a room from the list or create a new one at your current location. 📍
- 📐 Room discovery:
GET /api/v1/rooms/nearby?lat=..&lon=..fetches all active rooms and filters them with the Haversine formula against a configurableMAX_DISTANCE_KM. - ⏳ Expiry: rooms are lazily expired — any room whose
expires_athas passed is flipped to inactive the next time the nearby-rooms endpoint runs. - ⚡ Live messaging: the frontend opens
ws://.../ws/{room_id}, sends ajoinevent with{ nickname, client_id }, receives the existing message history, then sends/receivesmessageevents for the rest of the session. - 🧵 Connection management: an in-memory
ConnectionManagermapsroom_id → [sockets]andclient_id → room_id, broadcasting new messages to every socket in a room and blocking a client from being in two rooms at once. - 💾 Persistence: every message is written to SQLite via SQLModel before being broadcast, so history survives reconnects.
- 🔓 There's no real authentication yet — identity is just a nickname + a locally-stored
client_id.python-joseandpasslibare inrequirements.txtfor future JWT-based auth but aren't wired in yet. - 🗄️ Uses SQLite by default — fine for local dev/demo, but swap
DATABASE_URLfor Postgres before deploying anywhere with concurrent traffic. - 🌐 CORS is wide open (
allow_origins=["*"]) for local development — lock this down before deploying publicly. - 📍 Distance is calculated as-the-crow-flies (straight-line), not walking/driving distance.
Made with ☕ and a bit too much curiosity about who else is nearby.