π Food Delivery Platform Project
Build a Scalable MongoDB-Powered Food Delivery System
π― Project Overview
Welcome to the Food Delivery Platform project! In this comprehensive hands-on tutorial, you'll build a production-ready food delivery system similar to Uber Eats, DoorDash, or Swiggy. You'll implement restaurant management, real-time order tracking, geospatial queries for nearby restaurants, delivery partner assignment, and advanced analytics.
β±οΈ Estimated completion time: 3-4 hours | πͺ Difficulty: Intermediate to Advanced
This project introduces MongoDB's powerful geospatial features, time-series data for delivery tracking, and complex aggregation pipelines for multi-restaurant analytics. You'll learn patterns used by billion-dollar food delivery platforms.
π Meet QuickBite: Your Food Delivery Platform
The Challenge: Maria is launching QuickBite, a food delivery platform connecting hungry customers with local restaurants. The system needs to handle thousands of restaurants across multiple cities, match customers with nearby restaurants based on location, track orders in real-time, optimize delivery routes, and provide insights into food trends and restaurant performance.
Your Mission: As the database architect, you'll design a MongoDB backend that supports:
- β Restaurant onboarding with location-based discovery
- β Dynamic menu management with customizations
- β Real-time order placement and tracking
- β Delivery partner assignment and route optimization
- β Geospatial queries (find restaurants within 5km)
- β Rating and review system
- β Business analytics and reporting
π Learning Objectives
By completing this project, you will master:
πΊοΈ Geospatial Queries
Use 2dsphere indexes, $near, $geoWithin for location-based features
π¦ Embedded Documents
Model complex menu items with nested customizations and add-ons
β±οΈ Real-Time Tracking
Implement order status updates and delivery location tracking
π Advanced Aggregation
Build multi-stage pipelines for restaurant analytics
β‘ Query Optimization
Optimize geospatial queries and complex lookups
π Time-Series Data
Track delivery progress with location timestamps
π Database Schema Design
Our food delivery platform consists of 7 interconnected collections:
customers βββ¬ββ> orders βββ¬ββ> menuItems ββ> restaurants
β β
β βββ> deliveryPartners
β
βββ> reviews ββ> restaurants
Key Relationships:
β’ Orders reference customers, restaurants, and menuItems
β’ Reviews link customers to restaurants
β’ Delivery partners are assigned to orders
β’ Menu items belong to restaurants
πͺ restaurants - Restaurant Directory
Store restaurant information with geospatial location data
| Field | Type | Notes |
|---|---|---|
_id | ObjectId | Auto-generated |
name | String | Text indexed |
description | String | - |
cuisineType | Array[String] | Indexed |
location | GeoJSON Point | β 2dsphere index |
address | Object | {street, city, zipCode} |
phone | String | - |
openingHours | Object | {open, close} |
rating | Object | {average, count} |
isActive | Boolean | Indexed |
deliveryFee | Number | - |
minOrderAmount | Number | - |
π menuItems - Restaurant Menus
Food items with categories, prices, and customizations
- _id ObjectId
- restaurantId ObjectId (reference, indexed)
- name String (text indexed)
- description String
- category String (indexed)
- price Number
- image String
- isVegetarian Boolean
- isAvailable Boolean
- customizations Array [{name, options, type}]
- addOns Array [{name, price}]
π₯ customers - Customer Profiles
Customer information with delivery addresses
- _id ObjectId
- name String
- email String (unique, indexed)
- phone String
- addresses Array [{name, location (GeoJSON), isDefault}]
- createdAt Date
π orders - Customer Orders
Complete order information with items and delivery details
- _id ObjectId
- orderNumber String (unique, indexed)
- customerId ObjectId (reference, indexed)
- restaurantId ObjectId (reference, indexed)
- items Array [{menuItemId, name, quantity, price, customizations}]
- subtotal Number
- deliveryFee Number
- tax Number
- total Number
- status String (indexed)
- deliveryAddress Object with GeoJSON location
- deliveryPartnerId ObjectId (reference)
- createdAt Date (indexed)
- estimatedDeliveryTime Date
- deliveredAt Date
ποΈ deliveryPartners - Delivery Personnel
Delivery partner information with real-time location
- _id ObjectId
- name String
- phone String
- vehicleType String
- currentLocation GeoJSON Point (2dsphere)
- isAvailable Boolean (indexed)
- currentOrderId ObjectId
- rating Object {average, count}
β reviews - Ratings & Reviews
Customer feedback for restaurants and delivery
- _id ObjectId
- orderId ObjectId (reference)
- customerId ObjectId (reference)
- restaurantId ObjectId (reference, indexed)
- restaurantRating Number (1-5)
- foodRating Number (1-5)
- deliveryRating Number (1-5)
- comment String
- createdAt Date
π deliveryTracking - Location History
Time-series data for delivery route tracking
- _id ObjectId
- orderId ObjectId (reference, indexed)
- deliveryPartnerId ObjectId (reference)
- location GeoJSON Point
- timestamp Date (indexed)
- status String
- GeoJSON Points: All locations use GeoJSON format for geospatial queries
- Embedded Items: Order items are embedded to preserve historical menu prices
- Separate Tracking: Location history in dedicated collection for scalability
- Status Indexing: Quick filtering of active orders and available partners
π§ Initial Setup
1. Create the Database
use quickBite
// Verify creation
db.getName()
// Output: "quickBite"
2. Create Collections
db.createCollection("restaurants")
db.createCollection("menuItems")
db.createCollection("customers")
db.createCollection("orders")
db.createCollection("deliveryPartners")
db.createCollection("reviews")
db.createCollection("deliveryTracking")
3.Create Geospatial Indexes
// Restaurant location index (most important!)
db.restaurants.createIndex({ location: "2dsphere" })
// Customer address locations
db.customers.createIndex({ "addresses.location": "2dsphere" })
// Delivery partner location
db.deliveryPartners.createIndex({ currentLocation: "2dsphere" })
// Delivery tracking locations
db.deliveryTracking.createIndex({ location: "2dsphere" })
4. Create Other Essential Indexes
// Restaurants
db.restaurants.createIndex({ name: "text", description: "text" })
db.restaurants.createIndex({ cuisineType: 1 })
db.restaurants.createIndex({ isActive: 1 })
// Menu items
db.menuItems.createIndex({ restaurantId: 1 })
db.menuItems.createIndex({ name: "text" })
db.menuItems.createIndex({ category: 1 })
// Customers
db.customers.createIndex({ email: 1 }, { unique: true })
// Orders
db.orders.createIndex({ orderNumber: 1 }, { unique: true })
db.orders.createIndex({ customerId: 1, createdAt: -1 })
db.orders.createIndex({ restaurantId: 1, createdAt: -1 })
db.orders.createIndex({ status: 1 })
db.orders.createIndex({ createdAt: -1 })
// Delivery partners
db.deliveryPartners.createIndex({ isAvailable: 1 })
// Reviews
db.reviews.createIndex({ restaurantId: 1 })
db.reviews.createIndex({ orderId: 1 })
// Delivery tracking
db.deliveryTracking.createIndex({ orderId: 1, timestamp: 1 })
Your QuickBite database is now ready with all collections and geospatial indexes configured for location-based queries!
πͺ Phase 1: Restaurant Management
- Insert documents with GeoJSON locations
- Create 2dsphere indexes for spatial queries
- Query restaurants by cuisine and rating
- Update restaurant information
- GeoJSON Point format
- Coordinate ordering [lng, lat]
- Embedded documents for addresses
- Boolean and number field types
Add Restaurants with GeoJSON Locations
db.restaurants.insertMany([
{
name: "Pizza Paradise",
description: "Authentic Italian pizzas with wood-fired oven",
cuisineType: ["Italian", "Pizza"],
location: {
type: "Point",
coordinates: [-122.4194, 37.7749] // [longitude, latitude] - San Francisco
},
address: {
street: "123 Main Street",
city: "San Francisco",
state: "CA",
zipCode: "94102"
},
phone: "+1-415-555-0100",
openingHours: {
monday: { open: "11:00", close: "22:00" },
tuesday: { open: "11:00", close: "22:00" },
wednesday: { open: "11:00", close: "22:00" },
thursday: { open: "11:00", close: "22:00" },
friday: { open: "11:00", close: "23:00" },
saturday: { open: "11:00", close: "23:00" },
sunday: { open: "12:00", close: "22:00" }
},
rating: {
average: 4.5,
count: 328
},
isActive: true,
deliveryFee: 3.99,
minOrderAmount: 15
},
{
name: "Sushi Express",
description: "Fresh sushi and Japanese cuisine",
cuisineType: ["Japanese", "Sushi", "Asian"],
location: {
type: "Point",
coordinates: [-122.4200, 37.7750] // ~0.5 km from Pizza Paradise
},
address: {
street: "456 Market Street",
city: "San Francisco",
state: "CA",
zipCode: "94103"
},
phone: "+1-415-555-0101",
openingHours: {
monday: { open: "12:00", close: "21:00" },
tuesday: { open: "12:00", close: "21:00" },
wednesday: { open: "12:00", close: "21:00" },
thursday: { open: "12:00", close: "21:00" },
friday: { open: "12:00", close: "22:00" },
saturday: { open: "12:00", close: "22:00" },
sunday: { open: "12:00", close: "21:00" }
},
rating: {
average: 4.7,
count: 512
},
isActive: true,
deliveryFee: 4.99,
minOrderAmount: 20
},
{
name: "Burger Bliss",
description: "Gourmet burgers and American comfort food",
cuisineType: ["American", "Burgers", "Fast Food"],
location: {
type: "Point",
coordinates: [-122.4180, 37.7740] // ~1 km away
},
address: {
street: "789 Mission Street",
city: "San Francisco",
state: "CA",
zipCode: "94103"
},
phone: "+1-415-555-0102",
openingHours: {
monday: { open: "10:00", close: "23:00" },
tuesday: { open: "10:00", close: "23:00" },
wednesday: { open: "10:00", close: "23:00" },
thursday: { open: "10:00", close: "23:00" },
friday: { open: "10:00", close: "00:00" },
saturday: { open: "10:00", close: "00:00" },
sunday: { open: "10:00", close: "23:00" }
},
rating: {
average: 4.3,
count: 267
},
isActive: true,
deliveryFee: 2.99,
minOrderAmount: 12
},
{
name: "Tandoori Nights",
description: "Authentic Indian cuisine with traditional flavors",
cuisineType: ["Indian", "Curry", "Vegetarian"],
location: {
type: "Point",
coordinates: [-122.4220, 37.7760] // ~1.5 km away
},
address: {
street: "321 Valencia Street",
city: "San Francisco",
state: "CA",
zipCode: "94110"
},
phone: "+1-415-555-0103",
openingHours: {
monday: { open: "11:30", close: "22:00" },
tuesday: { open: "11:30", close: "22:00" },
wednesday: { open: "11:30", close: "22:00" },
thursday: { open: "11:30", close: "22:00" },
friday: { open: "11:30", close: "23:00" },
saturday: { open: "11:30", close: "23:00" },
sunday: { open: "11:30", close: "22:00" }
},
rating: {
average: 4.6,
count: 445
},
isActive: true,
deliveryFee: 4.49,
minOrderAmount: 18
}
])
Search Restaurants by Cuisine
// Find all Italian restaurants
db.restaurants.find({ cuisineType: "Italian" })
// Find restaurants with multiple cuisines (any match)
db.restaurants.find({ cuisineType: { $in: ["Indian", "Japanese"] } })
{
_id: ObjectId("..."),
name: "Pizza Paradise",
cuisineType: [ "Italian", "Pizza" ],
rating: { average: 4.5, count: 328 },
deliveryFee: 3.99,
minOrderAmount: 15,
...
}
π‘ What to notice: The query matches any document where the cuisineType array contains "Italian". MongoDB automatically searches within arrays without special syntax.
Update Restaurant Information
// Update delivery fee
db.restaurants.updateOne(
{ name: "Pizza Paradise" },
{ $set: { deliveryFee: 4.49 } }
)
// Mark restaurant as inactive
db.restaurants.updateOne(
{ name: "Burger Bliss" },
{ $set: { isActive: false } }
)
π Phase 2: Menu Management
- Model menu items with nested arrays
- Create customization options
- Reference restaurants with ObjectId
- Query by category and price
- Embedded documents for options
- Arrays of objects pattern
- Foreign key references
- Range queries on price
Create Menu Items
// Get restaurant ID first
var pizzaParadise = db.restaurants.findOne({ name: "Pizza Paradise" })
db.menuItems.insertMany([
{
restaurantId: pizzaParadise._id,
name: "Margherita Pizza",
description: "Classic pizza with fresh mozzarella, tomatoes, and basil",
category: "Pizza",
price: 14.99,
image: "/images/margherita.jpg",
isVegetarian: true,
isAvailable: true,
customizations: [
{
name: "Size",
options: [
{ value: "Small (10\")", priceModifier: -3 },
{ value: "Medium (12\")", priceModifier: 0 },
{ value: "Large (14\")", priceModifier: 4 },
{ value: "Extra Large (16\")", priceModifier: 7 }
],
type: "single",
required: true
},
{
name: "Crust",
options: [
{ value: "Thin Crust", priceModifier: 0 },
{ value: "Thick Crust", priceModifier: 1.5 },
{ value: "Stuffed Crust", priceModifier: 3 }
],
type: "single",
required: true
}
],
addOns: [
{ name: "Extra Cheese", price: 2.50 },
{ name: "Mushrooms", price: 1.50 },
{ name: "Olives", price: 1.50 },
{ name: "Pepperoni", price: 2.00 }
]
},
{
restaurantId: pizzaParadise._id,
name: "Pepperoni Pizza",
description: "Loaded with premium pepperoni and cheese",
category: "Pizza",
price: 16.99,
image: "/images/pepperoni.jpg",
isVegetarian: false,
isAvailable: true,
customizations: [
{
name: "Size",
options: [
{ value: "Medium (12\")", priceModifier: 0 },
{ value: "Large (14\")", priceModifier: 4 },
{ value: "Extra Large (16\")", priceModifier: 7 }
],
type: "single",
required: true
}
],
addOns: [
{ name: "Extra Pepperoni", price: 3.00 },
{ name: "JalapeΓ±os", price: 1.00 },
{ name: "Extra Cheese", price: 2.50 }
]
},
{
restaurantId: pizzaParadise._id,
name: "Garlic Breadsticks",
description: "Freshly baked breadsticks with garlic butter",
category: "Appetizers",
price: 5.99,
image: "/images/breadsticks.jpg",
isVegetarian: true,
isAvailable: true,
customizations: [],
addOns: [
{ name: "Marinara Sauce", price: 0.75 },
{ name: "Cheese Dip", price: 1.25 }
]
}
])
// Add Sushi Express menu
var sushiExpress = db.restaurants.findOne({ name: "Sushi Express" })
db.menuItems.insertMany([
{
restaurantId: sushiExpress._id,
name: "California Roll",
description: "Crab, avocado, and cucumber roll",
category: "Sushi Rolls",
price: 8.99,
image: "/images/california-roll.jpg",
isVegetarian: false,
isAvailable: true,
customizations: [
{
name: "Pieces",
options: [
{ value: "6 pieces", priceModifier: 0 },
{ value: "12 pieces", priceModifier: 7 }
],
type: "single",
required: true
}
],
addOns: [
{ name: "Wasabi", price: 0 },
{ name: "Ginger", price: 0 },
{ name: "Extra Soy Sauce", price: 0.50 }
]
},
{
restaurantId: sushiExpress._id,
name: "Salmon Nigiri",
description: "Fresh salmon over sushi rice",
category: "Nigiri",
price: 6.99,
image: "/images/salmon-nigiri.jpg",
isVegetarian: false,
isAvailable: true,
customizations: [
{
name: "Pieces",
options: [
{ value: "2 pieces", priceModifier: 0 },
{ value: "4 pieces", priceModifier: 6 },
{ value: "6 pieces", priceModifier: 11 }
],
type: "single",
required: true
}
],
addOns: []
}
])
Query Menu Items
// Get all menu items for Pizza Paradise
db.menuItems.find({ restaurantId: pizzaParadise._id })
// Get only available vegetarian items
db.menuItems.find({
restaurantId: pizzaParadise._id,
isVegetarian: true,
isAvailable: true
})
// Search menu items by name
db.menuItems.find({
restaurantId: pizzaParadise._id,
$text: { $search: "pizza" }
})
Update Menu Availability
// Mark item as out of stock
db.menuItems.updateOne(
{ name: "Margherita Pizza", restaurantId: pizzaParadise._id },
{ $set: { isAvailable: false } }
)
// Update item price
db.menuItems.updateOne(
{ name: "Garlic Breadsticks" },
{ $set: { price: 6.49 } }
)
π Phase 3: Customer Orders
- Create customers with multiple addresses
- Build complete order documents
- Update order status transitions
- Query orders by status and customer
- Embedded order items array
- Calculated fields (subtotal, tax, total)
- Status workflow management
- Timestamp tracking
Create Customer
db.customers.insertOne({
name: "John Doe",
email: "john.doe@email.com",
phone: "+1-415-555-9999",
addresses: [
{
name: "Home",
street: "150 Main Street, Apt 5B",
city: "San Francisco",
state: "CA",
zipCode: "94102",
location: {
type: "Point",
coordinates: [-122.4195, 37.7748] // Very close to Pizza Paradise
},
isDefault: true
},
{
name: "Office",
street: "500 Market Street, Floor 12",
city: "San Francisco",
state: "CA",
zipCode: "94105",
location: {
type: "Point",
coordinates: [-122.3985, 37.7900]
},
isDefault: false
}
],
createdAt: new Date()
})
Place an Order
var customer = db.customers.findOne({ email: "john.doe@email.com" })
var restaurant = db.restaurants.findOne({ name: "Pizza Paradise" })
var margherita = db.menuItems.findOne({ name: "Margherita Pizza" })
var breadsticks = db.menuItems.findOne({ name: "Garlic Breadsticks" })
var orderNumber = "QBO-" + new Date().getTime()
var defaultAddress = customer.addresses.find(addr => addr.isDefault)
// Calculate order totals
var subtotal = 14.99 + 4 + 2.50 + 5.99 + 0.75 // Pizza (Large) + Extra Cheese + Breadsticks + Sauce
var deliveryFee = restaurant.deliveryFee
var tax = subtotal * 0.0875 // 8.75% tax
var total = subtotal + deliveryFee + tax
db.orders.insertOne({
orderNumber: orderNumber,
customerId: customer._id,
restaurantId: restaurant._id,
items: [
{
menuItemId: margherita._id,
name: "Margherita Pizza",
quantity: 1,
basePrice: 14.99,
customizations: [
{ name: "Size", value: "Large (14\")", priceModifier: 4 }
],
addOns: [
{ name: "Extra Cheese", price: 2.50 }
],
itemTotal: 21.49 // 14.99 + 4 + 2.50
},
{
menuItemId: breadsticks._id,
name: "Garlic Breadsticks",
quantity: 1,
basePrice: 5.99,
customizations: [],
addOns: [
{ name: "Marinara Sauce", price: 0.75 }
],
itemTotal: 6.74 // 5.99 + 0.75
}
],
subtotal: 28.23,
deliveryFee: 3.99,
tax: 2.47,
total: 34.69,
status: "placed",
deliveryAddress: {
street: defaultAddress.street,
city: defaultAddress.city,
state: defaultAddress.state,
zipCode: defaultAddress.zipCode,
location: defaultAddress.location
},
deliveryPartnerId: null,
createdAt: new Date(),
estimatedDeliveryTime: new Date(Date.now() + 45 * 60000), // 45 minutes
deliveredAt: null
})
print("Order created: " + orderNumber)
Update Order Status
// Restaurant accepts order
db.orders.updateOne(
{ orderNumber: orderNumber },
{ $set: { status: "accepted" } }
)
// Order is being prepared
db.orders.updateOne(
{ orderNumber: orderNumber },
{ $set: { status: "preparing" } }
)
// Order ready for pickup
db.orders.updateOne(
{ orderNumber: orderNumber },
{ $set: { status: "ready_for_pickup" } }
)
// Order picked up by delivery partner
db.orders.updateOne(
{ orderNumber: orderNumber },
{
$set: {
status: "out_for_delivery",
pickedUpAt: new Date()
}
}
)
// Order delivered
db.orders.updateOne(
{ orderNumber: orderNumber },
{
$set: {
status: "delivered",
deliveredAt: new Date()
}
}
)
Query Orders
// Get customer's order history
db.orders.find({ customerId: customer._id }).sort({ createdAt: -1 })
// Get all orders for a restaurant
db.orders.find({ restaurantId: restaurant._id }).sort({ createdAt: -1 })
// Get active orders
db.orders.find({
status: { $in: ["placed", "accepted", "preparing", "out_for_delivery"] }
})
// Get orders from today
var today = new Date()
today.setHours(0, 0, 0, 0)
db.orders.find({
createdAt: { $gte: today }
}).sort({ createdAt: -1 })
ποΈ Phase 4: Delivery Partner Management
Add delivery partners and assign orders.
Add Delivery Partners
db.deliveryPartners.insertMany([
{
name: "Mike Rodriguez",
phone: "+1-415-555-1001",
vehicleType: "Motorcycle",
currentLocation: {
type: "Point",
coordinates: [-122.4190, 37.7745]
},
isAvailable: true,
currentOrderId: null,
rating: {
average: 4.8,
count: 523
}
},
{
name: "Sarah Chen",
phone: "+1-415-555-1002",
vehicleType: "Bicycle",
currentLocation: {
type: "Point",
coordinates: [-122.4210, 37.7755]
},
isAvailable: true,
currentOrderId: null,
rating: {
average: 4.9,
count: 687
}
},
{
name: "David Kumar",
phone: "+1-415-555-1003",
vehicleType: "Car",
currentLocation: {
type: "Point",
coordinates: [-122.4175, 37.7735]
},
isAvailable: false,
currentOrderId: ObjectId(), // Currently on delivery
rating: {
average: 4.7,
count: 412
}
}
])
Assign Delivery Partner to Order
var order = db.orders.findOne({ orderNumber: orderNumber })
var partner = db.deliveryPartners.findOne({ name: "Mike Rodriguez" })
// Assign partner to order
db.orders.updateOne(
{ _id: order._id },
{ $set: { deliveryPartnerId: partner._id } }
)
// Mark partner as busy
db.deliveryPartners.updateOne(
{ _id: partner._id },
{
$set: {
isAvailable: false,
currentOrderId: order._id
}
}
)
Track Delivery Location
// Delivery partner picks up order
db.deliveryTracking.insertOne({
orderId: order._id,
deliveryPartnerId: partner._id,
location: {
type: "Point",
coordinates: [-122.4194, 37.7749] // Restaurant location
},
timestamp: new Date(),
status: "picked_up"
})
// En route update 1
db.deliveryTracking.insertOne({
orderId: order._id,
deliveryPartnerId: partner._id,
location: {
type: "Point",
coordinates: [-122.4196, 37.7748]
},
timestamp: new Date(),
status: "en_route"
})
// En route update 2 (closer to destination)
db.deliveryTracking.insertOne({
orderId: order._id,
deliveryPartnerId: partner._id,
location: {
type: "Point",
coordinates: [-122.4195, 37.7748]
},
timestamp: new Date(),
status: "en_route"
})
// Delivered
db.deliveryTracking.insertOne({
orderId: order._id,
deliveryPartnerId: partner._id,
location: {
type: "Point",
coordinates: [-122.4195, 37.7748] // Customer location
},
timestamp: new Date(),
status: "delivered"
})
// Mark partner as available again
db.deliveryPartners.updateOne(
{ _id: partner._id },
{
$set: {
isAvailable: true,
currentOrderId: null,
currentLocation: {
type: "Point",
coordinates: [-122.4195, 37.7748]
}
}
}
)
Get Delivery Route History
// Get complete delivery route
db.deliveryTracking.find({
orderId: order._id
}).sort({ timestamp: 1 })
πΊοΈ Phase 5: Geospatial Queries
- Use $near to find closest restaurants
- Query within radius using $geoWithin
- Calculate distances with $geoNear
- Convert meters to kilometers
- $maxDistance in meters
- Radian conversion for circles
- Spherical vs planar calculations
- 2dsphere index requirement
2km radius 5km radius
β β
βββββΌββββ βββββΌββββ
β± β β² β± β β²
β π β π£ β β π β π£ β
β β² β β± β β β² β β± β
β π π β β π π β
β β± β β² β β β± β β² β
β π β π β β π β π β
β² β β± β² β β±
βββββΌββββ βββββΌββββ
$geoWithin (all within) $near (sorted by distance)
Returns: π π£ π π Returns: π β π£ β π β π
(unordered) (closest first)
π Legend:
β’ π = Customer location (John's home)
β’ π = Pizza Paradise (0.5 km away)
β’ π£ = Sushi Express (1.2 km away)
β’ π = Burger Bliss (1.8 km away)
β’ π = Tandoori Nights (2.3 km away)
Find Restaurants Near Customer
// Customer location (John's home)
var customerLocation = {
type: "Point",
coordinates: [-122.4195, 37.7748]
}
// Find restaurants within 5km, sorted by distance
db.restaurants.find({
location: {
$near: {
$geometry: customerLocation,
$maxDistance: 5000 // 5000 meters = 5 km
}
},
isActive: true
})
// Find all restaurants within 2km radius
db.restaurants.find({
location: {
$geoWithin: {
$centerSphere: [
[-122.4195, 37.7748], // [longitude, latitude]
2 / 6378.1 // radius in radians (2km / Earth radius)
]
}
},
isActive: true
})
Find Nearby Restaurants with Aggregation
// Find restaurants with distance in meters
db.restaurants.aggregate([
{
$geoNear: {
near: customerLocation,
distanceField: "distance",
maxDistance: 5000,
query: { isActive: true },
spherical: true
}
},
{
$project: {
name: 1,
cuisineType: 1,
rating: 1,
deliveryFee: 1,
distance: 1,
distanceKm: { $round: [{ $divide: ["$distance", 1000] }, 2] }
}
}
])
Find Restaurants by Cuisine Near Location
// Find Italian restaurants within 3km
db.restaurants.aggregate([
{
$geoNear: {
near: customerLocation,
distanceField: "distance",
maxDistance: 3000,
query: {
isActive: true,
cuisineType: "Italian"
},
spherical: true
}
},
{
$project: {
name: 1,
rating: 1,
deliveryFee: 1,
distanceKm: { $round: [{ $divide: ["$distance", 1000] }, 2] }
}
},
{ $sort: { "rating.average": -1 } }
])
Find Available Delivery Partners Nearby
// Restaurant needs a delivery partner
var restaurantLocation = {
type: "Point",
coordinates: [-122.4194, 37.7749]
}
// Find nearest available delivery partner within 2km
db.deliveryPartners.find({
currentLocation: {
$near: {
$geometry: restaurantLocation,
$maxDistance: 2000
}
},
isAvailable: true
}).limit(1)
- $near: Returns results sorted by distance (closest first)
- $geoWithin: Returns all results within boundary (no sorting)
- $geoNear: Aggregation stage that adds distance field
- Coordinates: Always [longitude, latitude] order in GeoJSON!
- Distance: $maxDistance is in meters for GeoJSON
π Phase 6: Analytics & Reporting
Extract business insights from your food delivery data.
Restaurant Performance
db.orders.aggregate([
{ $match: { status: "delivered" } },
{
$group: {
_id: "$restaurantId",
totalOrders: { $sum: 1 },
totalRevenue: { $sum: "$subtotal" },
avgOrderValue: { $avg: "$subtotal" }
}
},
{
$lookup: {
from: "restaurants",
localField: "_id",
foreignField: "_id",
as: "restaurant"
}
},
{ $unwind: "$restaurant" },
{
$project: {
_id: 0,
restaurantName: "$restaurant.name",
totalOrders: 1,
totalRevenue: { $round: ["$totalRevenue", 2] },
avgOrderValue: { $round: ["$avgOrderValue", 2] }
}
},
{ $sort: { totalRevenue: -1 } }
])
Peak Hours Analysis
db.orders.aggregate([
{
$group: {
_id: { $hour: "$createdAt" },
orderCount: { $sum: 1 },
avgOrderValue: { $avg: "$total" }
}
},
{
$project: {
_id: 0,
hour: "$_id",
orderCount: 1,
avgOrderValue: { $round: ["$avgOrderValue", 2] }
}
},
{ $sort: { hour: 1 } }
])
Popular Menu Items
db.orders.aggregate([
{ $match: { status: "delivered" } },
{ $unwind: "$items" },
{
$group: {
_id: {
menuItemId: "$items.menuItemId",
name: "$items.name"
},
timesSold: { $sum: "$items.quantity" },
revenue: { $sum: "$items.itemTotal" }
}
},
{
$project: {
_id: 0,
itemName: "$_id.name",
timesSold: 1,
revenue: { $round: ["$revenue", 2] }
}
},
{ $sort: { timesSold: -1 } },
{ $limit: 10 }
])
Delivery Partner Performance
db.orders.aggregate([
{
$match: {
status: "delivered",
deliveryPartnerId: { $ne: null }
}
},
{
$group: {
_id: "$deliveryPartnerId",
totalDeliveries: { $sum: 1 },
avgDeliveryTime: {
$avg: {
$divide: [
{ $subtract: ["$deliveredAt", "$pickedUpAt"] },
60000 // Convert to minutes
]
}
}
}
},
{
$lookup: {
from: "deliveryPartners",
localField: "_id",
foreignField: "_id",
as: "partner"
}
},
{ $unwind: "$partner" },
{
$project: {
_id: 0,
partnerName: "$partner.name",
totalDeliveries: 1,
avgDeliveryTime: { $round: ["$avgDeliveryTime", 1] },
rating: "$partner.rating.average"
}
},
{ $sort: { totalDeliveries: -1 } }
])
Revenue by Day
db.orders.aggregate([
{ $match: { status: "delivered" } },
{
$group: {
_id: {
year: { $year: "$createdAt" },
month: { $month: "$createdAt" },
day: { $dayOfMonth: "$createdAt" }
},
dailyRevenue: { $sum: "$total" },
orderCount: { $sum: 1 }
}
},
{
$project: {
_id: 0,
date: {
$concat: [
{ $toString: "$_id.year" },
"-",
{ $toString: "$_id.month" },
"-",
{ $toString: "$_id.day" }
]
},
revenue: { $round: ["$dailyRevenue", 2] },
orders: "$orderCount"
}
},
{ $sort: { date: -1 } }
])
Customer Insights
db.orders.aggregate([
{
$group: {
_id: "$customerId",
totalOrders: { $sum: 1 },
totalSpent: { $sum: "$total" },
avgOrderValue: { $avg: "$total" }
}
},
{
$lookup: {
from: "customers",
localField: "_id",
foreignField: "_id",
as: "customer"
}
},
{ $unwind: "$customer" },
{
$project: {
_id: 0,
customerName: "$customer.name",
email: "$customer.email",
totalOrders: 1,
totalSpent: { $round: ["$totalSpent", 2] },
avgOrderValue: { $round: ["$avgOrderValue", 2] }
}
},
{ $sort: { totalOrders: -1 } },
{ $limit: 10 }
])
β‘ Performance Optimization
πΊοΈ Geospatial Indexes
2dsphere indexes are critical for $near and $geoWithin queries to perform efficiently
π Compound Indexes
Create indexes like {restaurantId: 1, isAvailable: 1} for filtered menu queries
π― Query Optimization
Always filter by isActive, status before doing expensive operations
πΎ Embedded vs Referenced
Order items are embedded (snapshot), but restaurant is referenced
Optimize Geospatial Queries
// Check if geospatial index is being used
db.restaurants.find({
location: {
$near: {
$geometry: { type: "Point", coordinates: [-122.4195, 37.7748] },
$maxDistance: 5000
}
},
isActive: true
}).explain("executionStats")
// Look for "stage": "GEO_NEAR_2DSPHERE" in the winning plan
- Always have 2dsphere index before running geospatial queries
- Limit $maxDistance to reasonable values (5-10km for food delivery)
- Add { isActive: true } filter to avoid querying closed restaurants
- Use projection to limit fields returned in large collections
- Consider caching popular restaurant lists by location
π Advanced Challenges
Challenge 1: Surge Pricing
Implement dynamic delivery fees based on demand (number of active orders in area)
Challenge 2: ETA Calculator
Calculate accurate delivery time based on distance, traffic, and preparation time
Challenge 3: Zone Management
Create delivery zones using $geoWithin with polygons for different areas
Challenge 4: Smart Matching
Assign orders to delivery partners based on location, rating, and current load
Challenge 5: Loyalty Program
Track customer points, tier levels, and automatic discount application
Challenge 6: Heat Map
Generate delivery density heatmap by aggregating orders by geographic regions
- Surge Pricing: Use aggregation to count orders in last hour within radius, apply multiplier to base fee
- ETA: Create function that queries $geoNear distance, adds restaurant prep time (15-30 min), adds travel time (distance/speed)
- Zones: Use GeoJSON Polygon type with $geoWithin to check if address is in delivery zone
- Smart Matching: Aggregate available partners, calculate score based on distance + rating + active orders
- Loyalty: Add points field to customers, create tiers collection, use $lookup to apply tier discounts
- Heat Map: Use $geoNear with $bucket to group orders by distance ranges from city center