Data Models - mukular/food-delivery-feastfast--architecture GitHub Wiki
Data Models Documentation
This document describes all major MongoDB (Mongoose) data models used in the Food Delivery Platform, including their purpose, relationships, and design decisions.
Note: This document intentionally lists only the most important and behavior-defining fields required to understand system design, data relationships, and business logic.
Implementation-specific, operational, security-related, or auxiliary fields (such as location tracking, authentication helpers, defaults, and internal flags) are intentionally omitted and live in the actual Mongoose schema files.
1. User Model
Purpose
Represents all authenticated users in the system, including:
- Customers
- Sellers (restaurant owners)
- Delivery partners
- Admins
Key Responsibilities
- Authentication & authorization
- Role-based access
- Wallet management
- Session ownership
Fields
User {
_id: ObjectId
fullName: String
email: String (unique)
phone: String
password: String (hashed)
role: "customer" | "seller" | "delivery" | "admin"
wallet: Number
isDeleted: Boolean
createdAt: Date
updatedAt: Date
}
Notes
- Single user table for all roles β simpler auth & session logic
- Wallet is stored here for atomic updates
- Role determines dashboard & permissions
2. Shop (Restaurant) Model
Purpose
Represents a restaurant owned by a seller.
Relationships
- One seller β One shop
- One shop β Many items
- One shop β Many shopOrders (embedded in orders)
Fields
Shop {
_id: ObjectId
owner: ObjectId (User)
name: String
image: { url, publicId }
cuisineType: [String]
city: String
state: String
location: {
type: "Point",
coordinates: [lng, lat]
}
serviceRadiusMeters: Number
openingHours: [{
open: String // "09:00"
close: String // "23:00"
}]
rating: {
average: Number
count: Number
}
isActive: Boolean
isApproved: Boolean
isDeleted: Boolean
createdAt: Date
}
Design Decisions
- GeoJSON Point used for $geoNear
- serviceRadiusMeters protects sellers from far orders
- Ratings are denormalized for fast reads
3. Item (Menu Item) Model
Purpose
Represents individual menu items sold by a restaurant.
Fields
Item {
_id: ObjectId
shop: ObjectId (Shop)
name: String
description: String
image: { url, publicId }
price: Number
category: String
foodType: "veg" | "nonveg"
rating: {
average: Number
count: Number
}
available: Boolean
isDeleted: Boolean
createdAt: Date
}
Notes
- Items are queried frequently β indexed fields
- Availability allows temporary disabling without deletion
4. Order Model (Core Model)
Purpose
Represents a customer order, which may contain multiple shop orders.
Why this model is important
This is the heart of the system.
Fields
Order {
_id: ObjectId
user: ObjectId (User)
status: "created" | "confirmed" | "completed" | "cancelled"
paymentMethod: "wallet" | "cod" | "online"
paymentStatus: "pending" | "paid" | "refunded"
couponCode: String | null
subtotal: Number
tax: Number
deliveryFee: Number
totalAmount: Number
deliveryStats: {
latitude: Number
longitude: Number
address: String
}
shopOrders: [ShopOrder]
razorpayOrderId: String
razorpayPaymentId: String
createdAt: Date
}
5. ShopOrder (Embedded Subdocument)
Purpose
Represents one restaurantβs portion of an order.
Why embedded?
- Orders are always fetched with shopOrders
- Atomic updates per order
- Faster reads
Fields
ShopOrder {
_id: ObjectId
shop: ObjectId (Shop)
owner: ObjectId (User)
status: "pending" | "preparing" |
"ready" | "out_for_delivery" |
"delivered" | "cancelled"
shopOrderItems: [{
item: ObjectId (Item)
name: String
price: Number
quantity: Number
}]
subtotal: Number
deliveryFee: Number
tax: Number
payableAmount: Number
acceptedAt: Date
preparedAt: Date
deliveredAt: Date
}
Design Notes
- Order lifecycle is tracked per shop
- Timestamps allow:
- Analytics
- Time-to-deliver calculations
- Seller performance metrics
6. ShopCoupon Model
Purpose
Represents promotional offers created by sellers.
Fields
ShopCoupon {
_id: ObjectId
code: String (unique)
shop: ObjectId (Shop)
title: String
description: String
discountType: "percentage" | "fixed" | "free_item"
discountValue: Number
maxDiscountAmount: Number
freeItem: {
id: ObjectId (Item)
name: String
}
minOrderAmount: Number
startDate: Date
endDate: Date
isActive: Boolean
usageLimitPerUser: Number
usageLimitTotal: Number
usedCount: Number
applicableCategories: [String]
excludedItems: [{ id, name }]
userUsed: [{
userId: ObjectId
usedCount: Number
lastUsed: Date
}]
createdAt: Date
}
Notes
- Designed for complex real-world rules
- User usage stored inside coupon for fast validation
- Trade-off: document grows β acceptable for coupon scale
7. DeliveryAssignment Model
Purpose
Links delivery partners to specific shop orders.
Fields
DeliveryAssignment {
_id: ObjectId
order: ObjectId (Order)
shopOrderId: ObjectId
shop: ObjectId (Shop)
assignedTo: ObjectId (User)
status: "assigned" | "picked_up" | "delivered"
assignedAt: Date
pickedUpAt: Date
deliveredAt: Date
}
Why separate model?
- Delivery lifecycle is independent
- Cleaner tracking for delivery partners
- Easier future scaling (batch deliveries, routing)
8. Redis Data (Non-Persistent)
Stored Data Types
a) Sessions
session:{sessionId} β user data
b) Cached Queries
restaurants:{lat}:{lng}:{filters}
menu:{shopId}:{filters}
Why Redis?
- Fast reads
- Reduces DB load
- Centralized session control
9. Relationships Summary
User
βββ Shop (seller)
β βββ Item
β βββ Coupon
β
βββ Order
β βββ ShopOrder (embedded)
β β βββ Items
β
βββ DeliveryAssignment (delivery partner)
10. Key Data Design Principles Used
- Denormalization for performance
- Embedded documents for atomicity
- Read-heavy optimization
- Geo-spatial indexing
- TTL-based caching
- Real-world trade-offs documented
11. Why This Data Model is Strong
This schema supports:
- High read throughput
- Complex order flows
- Real-time updates
- Scalable analytics
- Clean separation of concerns
It closely mirrors production systems used in large-scale delivery platforms.