QuickCart is a modern, full-stack e-commerce application designed to simplify order processing and management. It features seamless user authentication via Clerk, efficient cart and order handling, and background processing of orders with Inngest event workflows.
This project demonstrates:
- User registration and authentication
- Dynamic cart management with real-time calculations
- Order creation with address selection and promo code support
- Asynchronous order processing using Inngest for scalable background jobs
- Seller dashboard to view and manage orders
- Integration with MongoDB for data persistence
Built using Next.js, MongoDB, and integrated with Clerk and Inngest, QuickCart offers a robust foundation for scalable e-commerce platforms.
- Node.js (v16+ recommended)
- npm or yarn
- MongoDB database (local or cloud e.g., MongoDB Atlas)
- Clerk account for authentication
- Inngest account for event handling
- Cloudinary account for Image handling
git clone https://github.com/yourusername/your-repo-name.git
cd your-repo-nameThis project uses the following main dependencies:
- Next.js (
next) β React framework for server-rendered apps and static sites. - React (
react,react-dom) β UI library. - Mongoose (
mongoose) β MongoDB object modeling for Node.js. - Clerk (
@clerk/nextjs) β Authentication and user management. - Axios (
axios) β Promise based HTTP client for API requests. - Inngest (
inngest) β Event-driven workflows and background jobs. - Cloudinary (
cloudinary) β Image and media management. - React Hot Toast (
react-hot-toast) β Notifications and toast messages. - Tailwind CSS (
tailwindcss) β Utility-first CSS framework for styling.
- ESLint (
eslint,eslint-config-next,@eslint/eslintrc) β Linting and code quality. - PostCSS (
postcss) β CSS processor for Tailwind and other plugins.
Run this command to install all dependencies:
npm install
# or
yarn installNEXT_PUBLIC_CURRENCY=$ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your_public_key_here
CLERK_SECRET_KEY=sk_test_your_secret_key_here MONGODB_URI='mongodb+srv://username:password@cluster0.mongodb.net/your-db-name' INNGEST_SIGNING_KEY='your_inngest_signing_key_here' INNGEST_EVENT_KEY='your_inngest_event_key_here'
CLOUDINARY_CLOUD_NAME='your_cloudinary_cloud_name' CLOUDINARY_API_KEY='your_cloudinary_api_key' CLOUDINARY_API_SECRET='your_cloudinary_api_secret'
npm run dev
# or
yarn devREFER FILES: AppContext.jsx, api/user/data/route.js,
- User β The person using the web app
- Client Component (AppContextProvider) β Runs in browser, calls API
- Next.js API Route (/api/user/data) β Server-side logic to get DB data
- MongoDB via User model β Database
- Clerk Auth β Validates and provides userId
-
User loads the app and signs in with Clerk.
-
AppContextProvider detects user from useUser() and calls fetchUserData().
-
fetchUserData():
- Checks if the user.publicMetadata.role is "seller" β sets isSeller.
- Gets an auth token via getToken() from Clerk.- Calls Axios GET /api/user/data with the token in the Authorization header.
-
API Route /api/user/data:
- Uses getAuth(request) from Clerk to extract userId from the request.
- Connects to MongoDB via connectDB().
- Finds the user document with User.findById(userId).
- Returns { success: true, user } or an error.
-
AppContextProvider receives the API response:
- Updates userData state.
- Updates cartItems state.
-
UI updates automatically with new userData and cartItems.
| Method | Route | Description |
|---|---|---|
POST |
/api/product/add |
Add a new product (sellers only) |
GET |
/api/product/list |
Fetch all products |
GET |
/api/product/seller |
Fetch sellerβs products (sellers only) |
| GET | /api/order/seller-orders |
Fetch all orders for authorized sellers |
REFER FOLDER : Product,
- Next.js (App Router API routes)
- MongoDB with Mongoose ORM
- Cloudinary for media storage
- Clerk for authentication and role management
- JavaScript / ES Modules
File: /app/api/product/add/route.js
Method: POST
Flow:
-
Authentication
- Uses
getAuth()from@clerk/nextjs/serverto getuserId. - Calls
authSeller(userId)to ensure only sellers can add products.
- Uses
-
Get Form Data
- Reads product fields:
name,description,category,price,offerPrice. - Reads uploaded images from
formData.
- Reads product fields:
-
Upload Images to Cloudinary
- Converts uploaded file streams into buffers.
- Uploads them to Cloudinary using
upload_stream.
-
Save Product to MongoDB
- Connects to MongoDB (
connectDB()). - Creates a new
Productdocument with images and details.
- Connects to MongoDB (
-
Response
- Returns JSON
{ success: true, product: "Upload successful", newProduct }.
- Returns JSON
File: /app/api/product/list/route.js
Method: GET
Flow:
- Connect to MongoDB.
- Fetch all products using
Product.find({}). - Return them in JSON:
{ success: true, products }.
File: /app/api/product/seller/route.js
Method: GET
Flow:
-
Authentication & Authorization
- Get
userIdfrom Clerk. - Verify seller role with
authSeller(userId).
- Get
-
Fetch Products
- Connect to MongoDB.
- Fetch all products (optionally could filter by
userIdfor seller-specific data).
-
Response
- Return
{ success: true, products }or{ success: false, message }.
- Return
-
Authentication & Authorization
- The API uses Clerk to authenticate the user via
getAuth(request)which returnsuserId. - The user is verified as a seller by calling a custom
authSeller(userId)function. - If the user is not a seller, the API returns an error with
success: falseand message"not authorized".
- The API uses Clerk to authenticate the user via
-
Database Connection
- Connect to MongoDB with
connectDB().
- Connect to MongoDB with
-
Fetch Orders
- Fetch all orders from the
Ordercollection. - Use
.populate('address items.product')to fetch detailed information for the orderβs address and product(s).
- Fetch all orders from the
-
Return Response
- Return the list of populated orders with
success: true. - If an error occurs, return
success: falseand the error message.
- Return the list of populated orders with
refer: app\seller\orders\page.jsx
- Fetches seller orders from the backend API endpoint
/api/order/seller-orders. - Uses the
getTokenmethod from your app context to include an Authorization header with the JWT token. - Displays a loading spinner while fetching.
- Displays a list of orders with the following details:
- Product names and quantities.
- Address information (full name, area, city, state, phone number).
- Order amount displayed with currency.
- Payment method (hardcoded as COD here), date, and payment status.
- Uses
react-hot-toastto show error messages if fetching fails. - Uses a
Footercomponent for page footer.
- Requires the user to be logged in (tracked via
userfrom app context). - Orders are shown in the order received from the backend.
- Uses Next.js
Imagecomponent to render a box icon for each order.
| Method | Route | Description |
|---|---|---|
GET |
/api/cart/get |
Get the current user's cart |
POST |
/api/cart/update |
Update the user's cart |
Flow:
-
Authentication
getAuth(request)retrieves the currentuserIdfrom Clerk.
-
Database Connection
connectDB()connects to MongoDB.
-
Fetch User Data
- Find the user by
userIdin theUsercollection. - Extract
cartItemsfrom the user document.
- Find the user by
-
Response
- Returns
{ success: true, cartItems }.
- Returns
Flow:
-
Authentication
- Get
userIdfrom Clerk.
- Get
-
Request Parsing
- Read
cartDatafrom the request body (JSON).
- Read
-
Database Update
- Find the user by
userId. - Replace
user.cartItemswithcartData. - Save the updated user document.
- Find the user by
-
Response
- Returns
{ success: true, message: "Cart updated successfully" }.
- Returns
REFER FOLDER: app/user files...
| Method | Route | Description |
|---|---|---|
GET |
/api/user/get-address |
Get the current user's saved addresses |
POST |
/api/user/add-address |
Add a new address for the current user |
Flow:
-
Authentication
getAuth(request)retrieves the currentuserIdfrom Clerk.
-
Database Connection
connectDB()establishes a connection to MongoDB.
-
Fetch Addresses
- Query the
Addresscollection with{ userId }. - Retrieve all address documents belonging to that user.
- Query the
-
Response
- Returns:
{ "success": true, "addresses": [...] }
- Returns:
Flow:
-
Authentication
- Get
userIdfrom Clerk.
- Get
-
Request Parsing
- Read the
addressobject from the request body (JSON).
- Read the
-
Database Insert
- Create a new document in the
Addresscollection using:{ ...address, userId }
- Create a new document in the
-
Response
- Returns:
{ "success": true, "message": "Address added successfully", "newAddress": { ... } }
- Returns:
| Method | Route | Description |
|---|---|---|
POST |
/api/order/create |
Create a new order for the current user (processed asynchronously via Inngest) |
GET |
/api/order/list |
Get all orders for the current authenticated user |
Flow:
-
Authentication
- The API uses
getAuth(request)(Clerk) to retrieve the current user'suserId.
- The API uses
-
Request Parsing
- The request body must include:
address: the selected address's IDitems: an array of items with{ product, quantity }
- If
addressis missing oritemsis empty, it returns:{ "success": false, "message": "Invalid data" }
- The request body must include:
-
Amount Calculation
- For each item in
items, the product document is fetched usingProduct.findById(item.product). - The total amount is calculated as the sum of
(product.offerPrice * quantity)for all items. - An additional 2% tax/fee is added to the total amount.
- For each item in
-
Send Order Event (Async Processing)
- Using
inngest.send(), an event named"order/created"is emitted with this payload:{ "userId": "user-id-here", "address": "address-id-here", "items": [...], "amount": 1234, "date": 1689200000000 }
- Using
-
Clear User Cart
- Finds the user by
userIdin the database (User.findById(userId)). - Clears the
cartItemsby setting it to{}and saves the user document.
- Finds the user by
-
Response
- Returns JSON:
{ "success": true, "message": "Order created successfully" }
- Returns JSON:
Triggered by: order/created event.
Flow:
-
Batch Processing
- This Inngest function collects up to 5 events or waits 5 seconds before processing.
-
Order Mapping
- Maps each event's data to a new order object with keys:
{ "userId": "...", "items": [...], "amount": "...", "address": "...", "date": "..." }
- Maps each event's data to a new order object with keys:
-
Database Connection and Insertion
- Connects to MongoDB (
connectDB()). - Inserts the mapped orders into the
Ordercollection usingOrder.insertMany().
- Connects to MongoDB (
-
Response
- Returns:
{ "success": true, "processed": <number_of_orders_inserted> }
- Returns:
Flow:
-
Authentication
- Uses
getAuth(request)to get theuserId.
- Uses
-
Fetch Orders
- Queries the
Ordercollection filtering byuserId. - Uses
.populate('address items.product')to fetch relatedAddressandProductdetails for each order.
- Queries the
-
Response
- Returns JSON with all the populated orders for the user:
{ "success": true, "orders": [...] }
- Returns JSON with all the populated orders for the user:
-
Schema Types:
Order.addressmust be anObjectIdreferencing the"Address"model for populate to work correctly.Order.items.productmust be anObjectIdreferencing the"Product"model.
-
Populate works only if ref and types are set correctly in your Mongoose schemas.
Thank you for checking out this project. If you run into any issues or have questions, feel free to open an issue or reach out. Happy coding and enjoy building with this e-commerce platform!