CampusCraves: food sharing without double-booking
A full-stack platform where students post surplus food, reserve items, and complete hand-offs — built by a 4-person team for a software engineering course. React Native on the front, FastAPI + MongoDB behind, and a reservation protocol designed so that two hungry students can never book the same sandwich.
20
REST endpoints across food, users, and reports routers
3-state
reservation lifecycle with atomic compare-and-set transitions
4-person
team; I built across backend routers and app screens
Architecture
An Expo/React Native app talks through a single API-client module to a FastAPI backend split into three routers, each owning one MongoDB collection. The backend runs on Render; the app consumed the deployed API throughout development.
Client
React Native + Expo
Marketplace, posting, reporting, NetID onboarding, notifications — all API calls funneled through apiService.js.
API
FastAPI routers
/api/food · /api/users · /api/report — Pydantic validation at every boundary.
Data
MongoDB
One collection per aggregate — food, users, reports — with conditional updates enforcing lifecycle invariants.
Domain model
User
- netId · email · googleId?
- role: user | admin
- postCount · reservationCount
FoodPost
- name · category · dietary info
- pickup location · expiration
- status · reservedBy · photo
Report
- postId · reporter · reported
- message
- reviewStatus · reviewedBy
- A User posts many FoodPosts, and reserves many others — both counted on their profile.
- A Report links two Users through the FoodPost it concerns; a server-side guard blocks duplicate reports.
- Admins review Reports through a pending → reviewed workflow with a reviewer audit trail.
The reservation lifecycle
Every food post carries a traffic-light status. The interesting part is what enforces it: each transition is a single conditional MongoDB update whose filter names the state it expects to leave.
When two users race for one item
The failure mode that motivated the design: two students tap “Reserve” at the same moment. Whoever's update matches first wins; the other request finds modified: 0, re-reads the item for an accurate error, and the app tells the truth.
Try it yourself
A live simulation of the reservation API, running the same invariants as the real backend. You're on the left; Maya is on the right. Reserve something, complete a pickup — or hit the race button and watch the server let exactly one of you win.
CampusCraves
You · kn2200
Veggie wrap
Available📍 D2 Dining Hall
Chocolate croissants ×3
Available📍 Library Café
Chicken biryani
Available📍 A5 Kitchen
CampusCraves
Maya · mp5510
Veggie wrap
Available📍 D2 Dining Hall
Chocolate croissants ×3
Available📍 Library Café
Chicken biryani
Available📍 A5 Kitchen
SERVER LOG — the conditional update decides every request
Waiting for requests…
What this shows
- 1
Push invariants into the database.
The no-double-booking rule lives in the update filter itself — the one place a race can't slip past — rather than in client checks or read-then-write logic.
- 2
Model the domain as a small state machine.
Three named states with explicit transitions made authorization rules simple to state and test: only the reserver may complete, only available items may be reserved.
- 3
Team-scale engineering practices.
Feature-branch workflow across a 4-person team, a pytest suite over every router, and a deployed backend (Render) the mobile app consumed throughout development.
Architecture docs, API reference, and the full codebase live in the repo.
komalnpn/campus-craves →