e_commerce_app
// 1. list out the criteria of an e-commerce web-app
- A landing page (the shop interface)
- products shelf (recommended products)
- a filter bar on the side (categories)
- product brief info cards (photo, productName, price, add to cart)
- product detail page (photo, description, price)
- buyer selection control (+ / - for quantities, add to cart)
- a cart
- a shipping cart list with product details
- checkout page
- payment page
- order page
- a user icon
- register page
- user profile page
- user address book
- order history
- Admin page for editing products
- JWT table for login session
// 2. set up necessary files for the web app
- Create front-end (views) and back-end folders
- npm init at the root of e_commerce_app
- create a git repository and git init at the root
- create a .gitignore file and a .dockerignore file
- create a Dockerfile and a Jenkinsfile for CI/CD
- test the pipeline set up
// 3. create a routes folder, a module folder and a controller folder
- create a product.js in routes
- create a pool.js in module (the pool will have a isJenkins check for docker database and local database)
- create a .env and npm install dotenv --save
- input the DB variables and dockerized database variables in .env
- do a test for the route with a sample
// 4. create a server at the backend 6. create a server.js and require express 7. export the app from server.js to index.js 8. app.listen in index.js 9. create a postgres database with tools like dbdiagram 10. import the queries from diagram (Dbeaver) for local development and testing 11. Insert a testing data
// Goal: Set up a Jenkins container and PostgreSQL database container using docker-compose, // so Jenkins can run CI pipelines with the database and your app
-
Make sure project structure looks something like this: E_commerce_app/ ├── backend/ │ ├── index.js │ ├── package.json │ ├── package-lock.json │ └── ... (your app code) ├── jenkins/ │ └── Dockerfile ├── docker-compose.yml ├── .dockerignore └── .gitignore
-
Create Dockerfile for Jenkins (at the root, jenkins/Dockerfile) This custom Jenkins image installs required tools and sets up your app code:
FROM jenkins/jenkins:lts
USER root
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - &&
apt-get install -y nodejs &&
npm install -g npmRUN apt-get update && apt-get install -y
git
python3
python3-pip
postgresql-client
lsb-release
curlRUN curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc https://download.docker.com/linux/debian/gpg &&
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.asc]
https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list &&
apt-get update && apt-get install -y docker-ce-cliUSER jenkins
WORKDIR /app
COPY --chown=jenkins:jenkins package*.json ./ RUN npm ci
COPY --chown=jenkins:jenkins . .
CMD ["/bin/bash"]
// 5. create a dockerized database for CI/CD
- create a docker-compose.yml in the root of project (for getting a postgresql image from docker)
- set up the database environments and volumes for persisting data changes in the docker-compose.yml
- use a different port from 5432 like 5433 for the dockerized psql image
- run the image (docker-compose up -d) and the database will be running in a container
- try to connect the database from Dbeaver (input the environment from docker-compose.yml)
- try to seed the database with a test with a seed.js
- if the database can be seeded, try to connect from the app
- connect the database from Jenkins for testing
// 6. Jenkins should have preinstalled elements
- node.js, git, python3, python3-pip, postgresql-client, express, jest, supertest, bcrypt (adding those in the dockerfile while creating the jenkins image)
// 7. connect Jenkins to Postgresql's network (database)
- docker network ls (checking the names of network)
- docker network connect e_commerce_app_postgres-net jenkins-blueocean (connect the database network to the jenkins container)
- docker network inspect e_commerce_app_postgres-net (verify the network connection. both jenkins-blueocean and postgres-local should be listed under Containers)
- docker exec -it jenkins-blueocean psql -h postgres-local -U [username] -d [database] (test the database connection, enter the env password, if successful, will be logged into Postgresql)
// 8. After connection, add global env variables in Jenkins system, edit the pool for isJenkins, write and update the Jenkinsfile
- modify the pool dynamically to switch between local and docker (isJenkins?)
- add global env variables in Jenkins system (variable names have to match with the dockerized database env names)
- added a host check in server.test.js
- if the variables can be accessed in the pipeline, it will print out in 'sh printenv | grep DOCKER_DB_'
- add a stage of check DB connection before stage of run tests
- when the server test and route tests are passed, move forward to write other routes and tests
// 9. create open api documents (before creating the rest of the routes in the backend)
- create api.yaml (in controller)
- create swaggerSpec.js (in routes)
- install and require "swagger-jsdoc", "swagger-ui-express", "yamljs", ""path", "express" in swaggerSpec.js
- create the path and load the api spec from the yaml file (const swaggerPath = path.join(__dirname, '../controller/api.yaml'); const swaggerDefinition = YAML.load(swaggerPath);)
- set up doc route (swaggerRouter.use('/', swaggerUi.serve, swaggerUi.setup(swaggerDefinition));)
- set up the header and return the spec (swaggerRouter.get('/', (req, res) => { res.setHeader('Content-Type', 'application/json'); res.send(swaggerSpec); });)
- export the swaggerRouter
- import the swaggerRouter from index.js
// 10. after created the swaggerSpec.js, write the api.yaml
- create one and two tags and test them in localhost
- add the routes to api.yaml while completing each route
// 11. completing the products route
- npm install uuid (for product id) in the test
- set the test to do post, get, put, delete a product (will have to add isAdmin check)
- use a custom middleware to check if the product exits (by product_id)
- if the routes work, update the api.yaml (should only shows get products for security reasons)
// 12. import middlewares in server.js
- morgan, bodyParser, errorHandler, cors, helmet, express-session, passport, connect-pg-simple
// 13. complete a departments route
- write a route, controller and tests for department
- prepare the get department select options in the front end in the future
- update the api.yaml with departments component
- try to seed the database with departments (revise the test if it is necessary)
- think about how to add products_departments when a product is added and ready to match the existing departments (should add the control in the product route? while the department ids and names were listed out in options and parsed in body) Come back to this part when it is combined to front end interface
// 14. complete a materials route
- write a route, controller and tests for materials
- more or less like the department route and update the api.yaml
- have to come back to modify when it is combined to front end interface
// 15. work on the Users route
-
Think about what should be included in the User interface
-
register, login, profile, address book, order review
-
It must have a isAdmin check to get user basic information
-
Create a Register route in usersController
-
Register: import a bcrypt for hashing the password when registering a new user (in server side)
-
write a test for registering a user in local method and test in postman
-
may not need to update the api.yaml since the user should have created a new account before using the api service
-
make a test for email has been taken
-
move on to the login and create a loginController
-
create a passport config file in Utilities folder
-
Login: import passport, passport-local.Strategy, passport-google-oauth2.Strategy in the passport config file
-
in the passportConfig.js, require the pool and bcrypt for checking password, uuid for generating new user_id
-
serialized and deserialized user in passportConfig.js
-
import the passport from passportConfig in loginController
-
create issueJWT.js in utilities folder
-
create JWTs table in database
-
update the seed.js and try to re create the database (seed.js and .env have to be on the same root)
-
work on the issueJWT before returning to loginController
-
in issueJWT, import jsonwebtoken, fs and pool
-
get the private key from the file
-
issue the JWT with created date, expired date, user id and provider in the payload
-
sign the token with payload by private key, using expired date and algorithm: 'RS256'
-
save the signed token in the database
-
return the token in function and module.exports the function
-
import and use express-session, and connect-pg-simple in server.js (for postgresql database)
-
create a session-middleware in server.js (create the session table if it is missing)
-
import pool for the session saving in server.js
-
debug issuejwt.js if the data format is not relative (the table may not be created yet)
-
import issuejwt.js in loginController.js
-
write a local login test and integrate it in the user.test.js
-
if local login test is passed, it is time to make a google login and how can it be tested (but front end is not ready yet)
-
make a profile page route and controller for user (it can help check the session and tokens)
-
make a logout route and controller but it can not be tested until the front end is ready
// It's time to make a front end
// set the folder and files
- on the root of the app, npm create vite@latest frontend -- --template react (vite seems faster than react app)
- select the framework and variant, cd frontend, npm install, and npm run dev
- in the frontend, index/main is the root (createRoot from react-dom/client), app is the container to hold all the pages, and react-dom gathers all the pages in the app
- set proxy in package.json (in the frontend if using react-app)
- However, Vite uses vite.config.js instead of package.json to save the proxy configuration
// basic set up for the pages and set up the routes
- create a pages folder and create HomePage.js
- install react-router-dom in the front end folder and import it in app.js
- import { BrowserRouter, Routes, Route } from react-router-dom, will be able to render the HomePage in main and enable the doors to different pages
// try to connect the backend to the frontend
- create products page
- install axios in the frontend root and import it on ProductPage
- useState and useEffect are used to store the products
- tests if the frontend can display the product information (may have to seed to the database with simple products)
- if the products can be displayed, think about the next steps
// Plan about the frontend structure
- install and create redux for state management
- use slices as controller in components and pages
- add bootstrap UI framework
// install and create redux store
- npm install @reduxjs/toolkit react-redux in the front end
- create store folder inside src in front end
- create store.js inside the store folder
- import { configureStore } from '@reduxjs/toolkit';
- create a store variable from configureStore, which can add reducer in
- import productsReducer from './slices/productsSlice' (will be created as below)
- export default store
// create slices folder and create productsSlice.js
- create slice folder inside src/store
- import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; inside the productsSlice.js
- create an initialState variable
- import axios from 'axios' for fetching data and store data in the state
- create a function from createAsyncThunk for fetching data with axios and export it
- create productsSlice from createSlice
- the productsSlice will have name, initialState, reducers: {extraReducers} (for changing the initialState)
- export actions and the reducer at the end of the page
// provide store in the main.js or index.js in the front end
- import { Provider } from 'react-redux';
- import { store } from './store/store';
- wrap the with
// use Redux in ProductsPage
- import { useEffect } from "react";
- import { useSelector, useDispatch } from "react-redux";
- import { fetchProducts } from "../store/slices/productsSlice";
- in productsPage function, const dispatch = useDispatch(); const { products, loading, error } = useSelector((state) => state.products);
- implement useEffect to fetchProducts
// install and implement react-bootstrap
- npm install react-bootstrap bootstrap (in the front end)
- in main.jsx, import bootstrap's CSS: import 'bootstrap/dist/css/bootstrap.min.css'; (save to be effective)
- in ProductsPage.js, import react-bootstraps components import { Container, Row, Col, Card, Spinner, Alert } from 'react-bootstrap';
- update the components
// if the redux and bootstrap works fine, the layout would have been different // review the webpage design. how should the webpage present