Building an Anonymous Location-Based Chat App with Auto-Deleting Data (Next.js + MongoDB)
Muhammad Yaqoob
Most chat apps collect everything — profiles, messages, history, location, soul, past life, etc. I wanted the opposite.
So I built Shadow: An anonymous, location-based chat app where rooms and messages disappear automatically after 24 hours.
No accounts. No personal data. Just raw thoughts tied to a place.
Let’s break how this actually works — technically.
Core Idea
The app is based on three simple rules:
- Users are anonymous (no signup, no auth)
- Chats are tied to real locations
- Everything auto-deletes after 24 hours
That’s it. Everything else is just implementation.
Tech Stack
- Next.js – frontend
- TypeScript – sanity
- Express.js – backend API
- MongoDB + Mongoose – data storage
- MongoDB GeoJSON – location queries
- TTL Indexes – auto-delete data
- Redis – rate limits & short-term data
Anonymous Users (No Accounts)
When a user opens the app:
- The frontend generates a random anonymous ID
- This ID is not stored permanently
- No email, no username, no password
Example idea:
const anonymousId = crypto.randomUUID()
This ID is sent with every message. That’s the only thing identifying a user.
Simple. Clean. No privacy headache.
Location-Based Rooms (MongoDB GeoJSON)
Each chat room is tied to a location, not a user.
Room Schema
const roomSchema = new mongoose.Schema(
{
name: {
type: String,
required: true
},
location: {
type: {
type: String,
enum: ["Point"],
required: true
},
coordinates: {
type: [Number], // [longitude, latitude]
required: true
}
}
},
{ timestamps: true }
)
Geo Index
roomSchema.index({ location: "2dsphere" })
This lets MongoDB answer questions like:
“Give me all rooms within 5km of this user”
MongoDB does the math. We just ask nicely.
Finding Nearby Rooms
Using $near queries:
Room.find({
location: {
$near: {
$geometry: {
type: "Point",
coordinates: [lng, lat]
},
$maxDistance: 5000
}
}
})
So users see only rooms around them (or they can jump to any location manually).
Auto-Deleting Rooms (TTL Index)
This is the most important part.
Rooms should die after 24 hours — automatically.
TTL Index on createdAt
roomSchema.index(
{ createdAt: 1 },
{ expireAfterSeconds: 60 * 60 * 24 } // 24 hours
)
What happens here?
- MongoDB watches
createdAt - After 24 hours → document is deleted
- No cron job
- No background worker
- No cleanup script
MongoDB handles it silently 😌
Messages with Smart Indexing
Messages belong to rooms and also expire.
Message Schema
const messageSchema = new mongoose.Schema(
{
roomId: {
type: mongoose.Types.ObjectId,
ref: "Room"
},
userId: {
type: String,
required: true
},
content: {
type: String,
required: true
}
},
{ timestamps: true }
)
Index for Fast Reads
messageSchema.index({ roomId: 1, createdAt: -1 })
Why this matters:
- Fetch latest messages fast
- Sorted by newest first
- Scales well when rooms grow
Why TTL Index Value Looked “Wrong”
You might see MongoDB show TTL as something like 18000 instead of 86400.
That’s normal.
MongoDB:
- Stores TTL internally
- Runs cleanup every ~60 seconds
- The number you see isn’t always exact
Nothing is broken. TTL still works.
Limiting Room Creation
To avoid spam:
- Each user can create max 3 rooms
- Tracked using Redis or in-memory counters
- Resets naturally when rooms expire
This keeps the system clean without auth.
Why This Architecture Works
- Privacy-first → nothing sensitive stored
- Low maintenance → TTL does cleanup
- Scales well → MongoDB handles geo + expiry
- Simple mental model → rooms = locations
No over-engineering. Just smart defaults.
Final Thoughts
Shadow isn’t about features. It’s about constraints:
- Time-limited
- Location-limited
- Identity-less
Those constraints create honest conversations.
And technically? MongoDB TTL + GeoJSON did most of the heavy lifting.
Let's work together
Have a project in mind or want to hire me as a freelancer? Let's discuss requirements and how I can help you scale.
Chat on WhatsAppEmail Me Instead
Drop your email and I'll get back to you shortly.