Project 2- Full-Stack Web & Data :
Cart + Account w/ MongoDB and Cloud Deployment
This is Group work
Start: Oct. 2
Due: Nov 13 @ START OF CLASS
Points: 170 points (see rubric on Canvas for Evaluation GUIDELINES ---NOTE THERE WILL BE PEER REVIEWS FOR THIS EVALUATION) + 20 points for Peer Review (to be discussed later)
******Peer Evaluated--how to do this******* (rubric on Canvas)
Project Details
This is a group project. In Project 1, you developed a client-side React Single Page Application (SPA) using static product data stored in a local products.json file.In Project 2, your group will select one member's Project 1 ad will extend that application into a full-stack web application by adding:
- Node.js
- Express
- MongoDB Atlas
- User accounts
- Shopping cart persistence
- Order processing
- Cloud deployment
Your React application from Project 1 should continue to serve as the front end. Instead of loading data from a local JSON file, it will communicate with your Express backend using HTTP requests. As with Project 1 design is important. You will have all of the previous views from last time but, now the forms in them will function with your Node+Express Backend.
Project 2 Architecture React Front End |
The additional functionality includes:
- Create Account View - The Create Account View sends a POST request to the Express backend which will dynamically recieve the form information (after client-side validaton) and this backend code should:
- perform server-side validate all information given is good
- check to see if the login or email provided does not currently exist in (the users collection) in your MongoDB database and if so respond appropriatelt
- if the login and email are unique then create a new user document in the users collection of your ModgoDB database and respond appropriately--specifically ask them to log in. Note: I will not ask you to verify the email address)
- Add password security - Passwords must never be stored as plain text. The Express backend must hash passwords using an appropriate password-hashing library such as bcrypt before storing them in MongoDB. Login must compare the submitted password with the stored password hash.
- Account View (prior to login)- Contains a form to ask for login/password and a link to Create Account View. If user logging in this view sends a POST request to the Express backend which will lookup the login/password in the MogoDB database and if valid or not. valid respond appropriately --if valid will go to Account View (logged in)
- Account View (logged in) - this is displayed after the user logs in successfully and gives a response that echo's the user's basic information and provides a "dashboard" experience where the user can edit their account info as well as an optoin to view orders and to log out. Editing their account information should include all information except their unique identifier in MongoDB (The database should identify the user by MongoDB’s permanent _id, not by the email address.)
----------------------------------
Welcome, Ben!
----------------------------------Username:
bshahEmail:
ben@email.comShipping Address
123 Main Street
Hayward, CA 94542Phone
(510) 555-1212-------------------------------
[ Edit Account ]
[ View Orders ]
[ Logout ]
- Create a MongoDB database on Atlas with Google with minimallyCollections called : products, users and orders. A word about orders -they are processed through a series of forms launched from the Cart View. Credit card information will be collection. However, store only the last four digits of the card and some basic payment information. The merchant typically never stores the customer's credit card number. Instead, a payment processor (such as Stripe, Square, PayPal, or Authorize.net) handles it and returns a payment token or transaction ID. As you are not a real company and do not have accounts with Stripe, PayPal, etc. we are not actually processing payment information. For security reasons, do not store complete credit card numbers or CVV security codes in MongoDB. Your checkout form may collect this information to simulate an online purchase, but only the card type and last four digits (or no payment information at all) should be stored with the order.
>>Note: you no longer have a products.json (from Project 1) in the code, everything comes from the database
Example product document in products collection
{ "_id": "67c145fa1234567890abcdef","sku": "SHOE-001",
"name": "TrailRunner X2",
"brand": "Peak Performance",
"category": "Running Shoes",
"price": 89.99,
"salePrice": 79.99,
"currency": "USD",
"description": "Lightweight road running shoe designed for everyday training.",
"longDescription": "The TrailRunner X2 combines a breathable engineered mesh upper with a lightweight foam midsole to provide comfort during daily training runs. A durable rubber outsole provides traction on pavement and light trails.",
"gender": "Women's",
"colors": [ "Red", "White", "Blue" ],"sizes": [ 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10 ],"weight": "9.4 oz",
"material": "Engineered Mesh",
"heelDrop": "8 mm",
"archSupport": "Neutral",
"waterResistant": false,
"quantityInStock": 37,
"rating": 4.7,
"numberOfReviews": 183,
"freeShipping": true,
"featuredProduct": true,
"newArrival": false,
"image": "trailrunner-x2.jpg",
"thumbnail": "trailrunner-x2-thumb.jpg" }Example user document in users collection
{
"_id": "67c123abc456",
"email": "oldemail@example.com",
"passwordHash": "...",
"firstName": "Ben",
"lastName": "Shah",
"phone": "510-555-1212",
"shippingAddress": {
"street": "123 Main Street",
"city": "Hayward",
"state": "CA",
"zip": "94542"
}
}Order info:
Order
│
├── Customer Information
├── Order ID & Date
├── Shipping Address
├── Payment Information
├── Items[]
│ │
│ ├── TrailRunner X2
│ ├── Mountain Hiker Pro
│ └── CityWalker Lite
│
├── Subtotal
├── Tax
├── Shipping
└── TotalExample order json
{ "_id": "ORD-1001","userId": "67c123abc456",
"orderDate": "2027-04-10T14:32:18Z",
"customer": { "firstName": "Ben", "lastName": "Shah", "email": "ben@example.com", "phone": "510-555-1212" },"shippingAddress": { "street": "123 Main Street", "city": "Hayward", "state": "CA", "zip": "94542" },"payment": { "cardType": "Visa", "last4": "1234" },"items": [
{ "productId": 1, "name": "TrailRunner X2", "brand": "Peak Performance", "size": 9, "color": "Red", "quantity": 2, "unitPrice": 79.99, "lineTotal": 159.98 },{ "productId": 8, "name": "Mountain Hiker Pro", "brand": "Peak Performance", "size": 10, "color": "Brown", "quantity": 1, "unitPrice": 149.99, "lineTotal": 149.99 },{ "productId": 15, "name": "CityWalker Lite", "brand": "Peak Performance", "size": 9.5, "color": "Black", "quantity": 1, "unitPrice": 99.99, "lineTotal": 99.99 }],
"subtotal": 409.96, "tax": 38.95, "shippingCost": 0.00, "total": 448.91,
"status": "Submitted" }
- Products - ProductList + ProductCard with Backend
The React ProductList and ProductCard components created in Project 1 will continue to be used.
In Project 2, product information should be retrieved from the Express backend rather than directly from the local products.json file.
The Express backend must provide product information to the React application as JSON and the general flow should be:
React ProductList
│
│ GET request
▼
Express REST API
│
▼
MongoDB products collection
- Cart
When the user selects a product, size, color, and quantity and clicks Add to Cart, the React application should add the selected item to the shopping cart.
Each shopping-cart item should include at least:
Product ID
Product name
Product image
Selected size
Selected color
Quantity
Unit price
The navigation bar should display the total number of items currently in the cart.
The shopping cart must persist during the user’s visit.
- Cart + Session Variables: use Express session variables,
The Express session stores the authoritative copy of the shopping cart. React synchronizes its displayed cart using HTTP requests.
React state manages the displayed cart; the Express session manages the server-side cart for the visit.
It does not conflict with React useState(). They serve different purposes:
- React useState()
= keeps cart data in the browser while the page is running - Express session
= keeps cart data on the server for that browser session
Recommended design:
- React useState()
-
React Cart State
│
│ fetch()
▼
Express Session Cart
│
▼
Saved for the current user/browser session
So React still needs:
const [cartItems, setCartItems] = useState([]);
When the application first loads:
React sends GET /api/cart
│
▼
Express reads req.session.cart
│
▼
Express returns cart as JSON
│
▼
React stores it in useState()
When the user adds a product
ProductCard
│
▼
POST /api/cart
│
▼
Express updates req.session.cart
│
▼
Express returns updated cart
│
▼
React updates cartItems stateWHY both React useState & Express Sessions:
React useState() is needed so the interface updates immediately:
- cart count changes,
- Cart View rerenders,
- subtotal changes,
- quantities update.
The Express session is needed so the cart survives:
- navigation between views,
- browser refreshes,
- multiple API requests,
- temporary server-side storage before checkout.
- Order Confirmation Information
Order ID
Order Date
Customer name and email
Shipping address
Billing address
Card type and last four digits
Each ordered item
Selected size and color
Quantity
Unit price
Line total
Subtotal
Tax
Shipping
Final total
- Filestructure. Now you are adding a backend your structure will look something like:
where the choosen Project 1 code will now be contained under client and the NodeJS+Express will be stored in server. Depending on your choice of IDE the exact organization may vary,
but separation between React client and Express server must be clear (and I want top level client and server directories)
project-root/
│
├── client/
│ ├── src/
│ │ ├── components/
│ │ ├── views/
│ │ ├── App.jsx
│ │ └── main.jsx
│ ├── public/
│ ├── package.json
│ └── vite.config.js
│
├── server/
│ ├── controllers/
│ │ ├── accountController.js
│ │ ├── productController.js
│ │ ├── cartController.js
│ │ └── orderController.js
│ │
│ ├── routes/
│ │ ├── accountRoutes.js
│ │ ├── productRoutes.js
│ │ ├── cartRoutes.js
│ │ └── orderRoutes.js
│ │
│ ├── middleware/
│ ├── app.js
│ └── package.json
│
├── README.md
└── .gitignore
- Required Express API
GET /api/products
GET /api/products/:idPOST /api/accounts
POST /api/login
POST /api/logout
GET /api/account
PUT /api/accountGET /api/cart
POST /api/cart
PUT /api/cart/:itemId
DELETE /api/cart/:itemIdPOST /api/orders
GET /api/orders
GET /api/orders/:idGET /api/products
Returns the complete collection of products stored in the MongoDB products collection.
The React Shop View uses this endpoint to retrieve all available products and display them using the ProductList and ProductCard components.
GET /api/products/:id
Returns the information for one specific product.
The React Product Detail View uses this endpoint to retrieve detailed information about the selected product before displaying it.
POST /api/accounts
Creates a new user account.
The Express backend must:
- validate all submitted information
- verify that the email address is unique
- hash the password before storing it
- create a new document in the users collection
- return an appropriate success or error response
POST /api/login
Authenticates a user.
The backend verifies the submitted email and password. If authentication succeeds, an Express session should be created that stores the user's MongoDB _id.
>>Login must compare the submitted password with the stored password hash.
>>After successful login, the Express backend should create a session containing the user’s MongoDB _id. (Session --> userId: MongoDB user _id)
POST /api/logout
Logs the current user out of the application.
The Express session should be destroyed, and the user should be returned to the Home or Login view.
GET /api/account
Returns the account information for the currently logged-in user.
The React Account View uses this endpoint to display the user's profile, shipping address, and other account information.
PUT /api/account
Updates the currently logged-in user's account information.
The backend should validate the submitted data and verify that any new email address is unique before updating the corresponding document in the users collection.
GET /api/cart
Returns the current shopping cart stored in the Express session.
The React application uses this endpoint when it loads or refreshes to synchronize its cart state with the server.
POST /api/cart
Adds a product to the shopping cart.
The backend should validate the request, add the selected product (including selected options such as size and color) to the Express session cart, and return the updated shopping cart.
PUT /api/cart/:itemId
Updates an existing item in the shopping cart.
Typical updates include increasing or decreasing the quantity of a product.
The backend should validate the updated quantity before saving the modified cart.
DELETE /api/cart/:itemId
Removes one item from the shopping cart.
After removing the item, the backend should return the updated shopping cart.
POST /api/orders
Creates a new order.
The backend must:
- verify that the shopping cart is not empty
- retrieve the current user information
- validate all checkout information
- calculate the order totals
- create a new order document in the MongoDB orders collection
- clear the Express session shopping cart
- return an appropriate success response
The React application should then display an Order Confirmation View.
GET /api/orders
returns only the current user's orders
- Express Controllers.
Organize Express controller functions by responsibility. At minimum, provide controllers for accounts, products, shopping cart operations, and orders.
Each controller should receive an HTTP request, perform required validation and database operations, and return an appropriate JSON response. (Controllers should NOT contain HTML. Controllers return JSON responses.)
accountController.js
productController.js
cartController.js
orderController.js
- Server Side Validation
The Express backend must validate all incoming data. Client-side React validation alone is not sufficient.
Server-side validation should verify, as appropriate:
Required fields are present
Email has an appropriate format
Email is unique when creating or editing an account
Quantities are valid positive integers
Products exist
Submitted product prices are not trusted but looked up & cart total recalculated DO NOT just take submitted cart info
The cart is not empty
Addresses contain required fields
Order totals are calculated by the server
- Connecting to Database
When open connection?
Establish the MongoDB connection when the Express application starts and reuse that connection for incoming requests. Do not repeatedly open and close a new database connection inside every controller function. You should add this logic to your async function startServer() in the server.js file. You will add the a connection url to the environment variables in your Google Cloud Run configuration like the following. MONGODB_URI=mongodb+srv://yourLogin:yoiurPassword@cluster0.mongodb.net/StoreDB that you can access via process.env.MONGODB_URI in your startServer() function. The URI is specific to your MongoDB collection and this is just an example.In the Google Cloud Console:
Cloud Run
↓
YOUR Deployed Service Name
↓
Edit & Deploy New Revision
Scroll to Environment Variables & add new variable with URI>>>Alternatively you can choose to use Google Secrets Manager (and this is for you to learn on your own)
>>>During development, store your MongoDB connection string in a .env file (NEVER push to GitHub). When deploying to Google Cloud Run, configure MONGODB_URI as an environment variable in the Cloud Run service.
Each Cloud Run instance creates and reuses its own MongoDB connection pool.
Express starts↓
Open MongoDB connection
↓
Request
↓
Use connection
↓
Request
↓
Use connection
↓
Request
↓
Use connection
So your Express Application consists of:
----------------------------
Routes
Controllers
Sessions
Database Connection
----------------------------
When close connection?
when the Express Application is stopped (either as and admin you stop it or for some reason Google Cloud Run stops it) you should clean up and close the connection.
What is happening when Google Cloud Run creates mutiple instances of Express Application?
Note Google Cloud Run can scale and create multiple Express application instances ---in this case each Cloud Run instance creates and reuses its own MongoDB connection pool.
- Error Handling - your code must appropriately handle errors / exceptions
- Code Quality and Style
Your project should be written using professional software engineering practices.
General Requirements:- Code should be sufficiently commented.
- Use meaningful, descriptive names for variables, functions, components, classes, files, and directories.
- Follow a consistent naming convention throughout the project.
- Avoid duplicated code whenever possible by creating reusable React components and helper functions.
- Keep individual functions reasonably short and focused on one responsibility.
- Organize code appropriately (See above)
- Maintain consistent formatting throughout the project. (consider lower Camel case or similar)
- Deployment - both front-end and back-end to Google Cloud Run & related code development. DEPLOYMENT GUIDE
Deploy the complete full-stack application to Google Cloud Run as one service. Before deployment, build the React application using Vite. The Express server must serve the generated React static files and provide the required /api endpoints. The deployed Cloud Run service will therefore host both the React front end and the Express backend. MongoDB Atlas will remain the external database.
You need to make sure that the Express application checks in order:
1. Express checks /api routes
2. Express checks for static React files
3. Other requests receive React's index.html
So: GET /api/products is handled by Express while GET /shop is handled by React's index.html after which React displays the Shop View.
One URL for everythingThe deployed application could have one URL like the following https://cs351-store-xxxxx.run.app
>>>> React loads from https://cs351-store-xxxxx.run.app/>>>> The API is available from the same service for example https://cs351-store-xxxxx.run.app/api/products
>>>> React can therefore use relative URLs: const response = await fetch("/api/products");
NOTE: while we could deploy the front-end and back-end seperately and this could be done in industry (especially if different teams maintaining) I am making it simpler for you
GITHUB
- Create a new group Github repository and share your NodeJS+Express project to this repository
- Have README.md that must include Project overview, Group members, Build instructions,Deployment instructions
- Create a docs directory and publish to Github pages documentation to teach others about your project and code in the repository.
- PROJECT MANAGEMENT: Create a Issues Board for your Github respository and for EVERY ITEM listed above under project details you are to create MULTIPLE cards per item (there are multiple pieces to most of the items listed above). Assign people (not everyone to every card) to the cards AND set due dates for each card. During every group meeting working on this project you need to update your issues board.
- Create a Wiki in your Github repository that contains
- home page = link to the website
- create Account demo = video showing the before and after of the MongoDB AND a user using the website to create a new account.
- shopping + viewcart = video showing the shopping cart demo where the user goes to different product pages, adds them to a cart and at different times during shopping views the cart which shows a listing of all of the current items in the cart (view cart)
- checkout demo= show the before and after screenshots of any forms used to collect user information of address and billing as well as the before and after of the conent in the orders collection.
Deliverables:
- November 13 @ START OF CLASS
-
Turn in BOTH URL to the Github AND the URL to the Google Cloud Run app to Post to Canvas->Assignments->Project 2
WILL Present work Nov 13 in class (continuing as necessary next class) + Peer Reviews due Nov. 20
Regarding peer reviews, the instructor will review the peer reviews, if appropriate the group will maintain the average of the peer reviews given (only a subset of students will review your work) ---however, the instructor WILL alter the review score as necessary to reflect properly the work performed (and this can be to increase OR descrease the score). Also, points are awarded for doing peer reviews and if improper reviews are done or poor quality reviews are done, a student doing so will loose those points. ******Peer Evaluated--how to do this******* (rubric on Canvas)
-
