
Local Chef Bazaar
A full-stack food marketplace connecting customers with home cooks.
A marketplace built as a React single-page app on Vite, talking to an Express and Mongoose API. Menu browsing, ordering, reviews, role-based workflows, Stripe checkout and Firebase-backed authentication and storage.
- Role
- Built both sides: the React client and the Express API.
- Source status
- Verified — Both repositories are public. Every technology listed was read from their package manifests, and the deployed site was inspected.
+4 more in the case study
3 min read · 9 sections · 11 technologies
Local Chef Bazaar connects customers with independent home cooks. A cook publishes a menu, a customer orders from it, and the platform handles the payment and the review loop in between.
Both halves are public, so this case study is written directly from the source rather than from a description.
What the product does
Menu browsing and ordering
Menus are published per cook and become the catalogue a customer orders from.
Reviews
Customer reviews close the loop after an order is delivered.
Role-based workflows
JWT authentication on the API plus Firebase on the client separates customer, cook and administrative paths.
Stripe checkout
Both repositories depend on Stripe, and the client loads Stripe.js and the React bindings.
Dashboards with charts
Recharts is used for the reporting views behind the dashboards.
Architecture
- 01React SPAVite build, React Router 6
- 02Express APIExpress 5, single entry point
- 03MongoDBvia Mongoose 9
- 04StripePayment intents
- 05FirebaseAuth and storage
The API is a single index.js entry point deployed to lcb-server.vercel.app, with vercel.json in the repository to route requests to it. The client is a Vite build whose entire body is a single empty mount point, which is the normal shape for a client-rendered single-page application.
{
"name": "localchefbazaar-server",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.2.1",
"firebase-admin": "^13.6.0",
"jsonwebtoken": "^9.0.3",
"mongodb": "^7.0.0",
"mongoose": "^9.0.2",
"stripe": "^20.1.0"
}
}{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@stripe/react-stripe-js": "^5.4.1",
"@tanstack/react-query": "^5.90.12",
"axios": "^1.13.2",
"firebase": "^12.7.0",
"react": "^19.2.0",
"react-hook-form": "^7.68.0",
"react-router-dom": "^6.30.2",
"recharts": "^3.6.0",
"stripe": "^20.1.0"
}
}Engineering notes
Server state and client state
The client uses TanStack Query for everything that lives on the API and React context for everything that does not, which is the split I try to hold everywhere. Cart contents, the signed-in user and Stripe's publishable-key handshake are client state. Menus, orders and reviews are server state and should be cached, deduplicated and invalidated by key rather than mirrored into a global store.
Authorization belongs to the API
The API signs its own JWTs with jsonwebtoken and reads them with cookie-parser. Hiding a cook's dashboard in the React router is a usability feature, not a security control: the route handlers have to reject the request themselves, because anything in the browser can be edited by the person running it.
Payments
Stripe appears on both sides of the wire, which is the correct shape: the client needs Stripe.js to mount the payment element, and the server needs the secret key to create and confirm the intent. No secret belongs in the client bundle, and VITE_-prefixed variables are the ones a Vite build will publish, so only the publishable key goes there.
Trade-offs
- A client-rendered SPA. Simple mental model and cheap navigation, but the served HTML contains only an empty mount point, so the marketplace's pages are not indexable and there is nothing to render before JavaScript runs.
- Two token systems. Firebase on the client, JWT on the API. Convenient for Firebase features, at the cost of a second thing to keep valid.
- One server entry file. Fast to start and easy to deploy as a single function, but it is the first thing I would split when the route count grows.
Verification status
Links
- local-chef-bazar.vercel.appDeployed React client(opens in a new tab)
- lcb-server.vercel.appDeployed Express API(opens in a new tab)
- github.com/aamiqram/Assignment_11-ClientPublic client repository(opens in a new tab)
- github.com/aamiqram/Assignment_11-ServerPublic server repository, default branch main(opens in a new tab)