E-Commerce Website
React, Redux, MongoDB, Node, Express, BootstrapWARNING ABOUT LOADING TIME!
Be aware that this project is hosted with a free tier service. This means that it will take a while for the server to spin up when it's accessed. Thank you for your patience!
Description
Fully functional fullstack e-commerce website.
- State management with Redux.
- Route and controller logic with JWT authentication.
- Admin area with CRUD functionality for users, products and orders.
- MongoDB.
- Pages including: product listing, product detail page with reviews, user login, payment with Paypal, shipment, etc.
Frontend Diagram
Store
Combines all reducers and apply thunk (middleware that allows you to return functions).
View code
1const reducer = combineReducers({ 2 productList: productListReducer, 3 //followed by all other reducers... 4}); 5 6const cartItemsFromStorage = localStorage.getItem('cartItems') 7 ? JSON.parse(localStorage.getItem('cartItems')) 8 : []; 9 10//followed by other localStorage variables... 11 12const initialState = { 13 cart: { 14 cartItems: cartItemsFromStorage, 15 shippingAddress: shippingAddressFromStorage, 16 }, 17 userLogin: { userInfo: userInfoFromStorage }, 18}; 19 20const middleware = [thunk]; 21 22const store = createStore( 23 reducer, 24 initialState, 25 composeWithDevTools(applyMiddleware(...middleware)) 26); 27 28export default store; 29
Thunk
Redux Thunk is middleware that allows you to return functions, rather than just actions, within Redux. This allows for delayed actions, including working with promises. One of the main use cases for this middleware is handling asynchronous actions like using axios to send a GET request as seen in the Actions API call.
Constants
Just holds the constants names. The cases are: REQUEST, SUCCESS, FAIL, RESET.
Actions
Using async try/catch it fetches APIs and dispatches a type (e.g. REQUEST, SUCCESS, FAIL, RESET) that hits a Switch Case in the Reducer.
View code
1export const listProducts = 2 (keyword = '', pageNumber = '') => 3 async (dispatch) => { 4 try { 5 dispatch({ 6 type: PRODUCT_LIST_REQUEST, 7 }); 8 9 const { data } = await axios.get( 10 `/api/products?keyword=${keyword}&pageNumber=${pageNumber}` 11 ); 12 13 dispatch({ 14 type: PRODUCT_LIST_SUCCESS, 15 payload: data, 16 }); 17 } catch (error) { 18 dispatch({ 19 type: PRODUCT_LIST_FAIL, 20 payload: 21 error.response && error.response.data.message 22 ? error.response.data.message 23 : error.message, 24 }); 25 } 26 }; 27
Reducers
Takes previous state and action with its Switch Case and returns next state.
View code
1export const productListReducer = (state = { products: [] }, action) => { 2 switch (action.type) { 3 case PRODUCT_LIST_REQUEST: 4 return { loading: true, products: [] }; 5 case PRODUCT_LIST_SUCCESS: 6 return { 7 loading: false, 8 products: action.payload.products, 9 pages: action.payload.pages, 10 page: action.payload.page, 11 }; 12 case PRODUCT_LIST_FAIL: 13 return { loading: false, error: action.payload }; 14 default: 15 return state; 16 } 17}; 18
Screens
useDispatch & useSelector are both Redux Hooks.
useDispatch will trigger the action and useSelector will receive the state.
Components
Some components also use Redux, like ProductCarousel and Header.
Backend Diagram
Server
The heart of the backend application. Connects to the DB via config/db and uses all routes made available by routes (order, product, user, upload) and error handling directly from the middleware.
Config/db
Makes the connection to the database.
Routes
Best regarded as Endpoints. Route methods (get, post, put, delete) are defined for the specified route and used in conjunction with a controller and middleware functions, which hold the logic. (e.g. router.post('/login', authUser))
Controllers
Best regarded as the application logic. The functions defined here will be requested when hitting the defined routes/endpoints. It is the place where the logic for a given route is applied.
Middleware
Either called directly from server.js for errorHandling or from a given route, in this case userRoute, for both protect and isAdmin functions. protect will make sure that the user is logged in by verifying their bearer token (which is generated by the util). isAdmin will make sure that the given user has admin rights.
Utils
Generates a token which is called from the userController. (e.g. when logging in a user)
Models
Defines the DB schema for a given model. userModal.js also uses bcrypt to compare and hash passwords.