├── .gitignore ├── App ├── backend │ ├── .gitignore │ ├── routes │ │ └── peopleRoutes.js │ ├── database │ │ ├── config.js │ │ └── ddl.sql │ ├── package.json │ ├── server.js │ └── controllers │ │ └── peopleController.js └── frontend │ ├── src │ ├── pages │ │ ├── HomePage.jsx │ │ └── PeoplePage.jsx │ ├── App.css │ ├── main.jsx │ ├── App.jsx │ ├── components │ │ ├── navbar │ │ │ └── NavBar.jsx │ │ └── bsg_people │ │ │ ├── PersonTable.jsx │ │ │ ├── PersonTableRow.jsx │ │ │ ├── CreatePerson.jsx │ │ │ └── UpdatePerson.jsx │ └── assets │ │ └── react.svg │ ├── .gitignore │ ├── index.html │ ├── vite.config.js │ ├── .eslintrc.cjs │ ├── reactServer.cjs │ ├── package.json │ └── public │ └── vite.svg ├── images-readme ├── api.png ├── addperson.png ├── bsgpeople.png ├── homescreen.png └── updateperson.png └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | .env -------------------------------------------------------------------------------- /App/backend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .env -------------------------------------------------------------------------------- /images-readme/api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osu-cs340-ecampus/react-starter-app/HEAD/images-readme/api.png -------------------------------------------------------------------------------- /images-readme/addperson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osu-cs340-ecampus/react-starter-app/HEAD/images-readme/addperson.png -------------------------------------------------------------------------------- /images-readme/bsgpeople.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osu-cs340-ecampus/react-starter-app/HEAD/images-readme/bsgpeople.png -------------------------------------------------------------------------------- /images-readme/homescreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osu-cs340-ecampus/react-starter-app/HEAD/images-readme/homescreen.png -------------------------------------------------------------------------------- /images-readme/updateperson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osu-cs340-ecampus/react-starter-app/HEAD/images-readme/updateperson.png -------------------------------------------------------------------------------- /App/frontend/src/pages/HomePage.jsx: -------------------------------------------------------------------------------- 1 | function HomePage() { 2 | return

Feel free to add any information you like about your project

; 3 | } 4 | 5 | export default HomePage; 6 | -------------------------------------------------------------------------------- /App/frontend/src/App.css: -------------------------------------------------------------------------------- 1 | html { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | text-align: center; 6 | } 7 | 8 | html li { 9 | list-style-type: none; 10 | } 11 | -------------------------------------------------------------------------------- /App/frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import { BrowserRouter } from "react-router-dom"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root")).render( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /App/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .env 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /App/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /App/frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import dotenv from 'dotenv' 4 | 5 | dotenv.config() 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [react()], 10 | esbuild: { 11 | loader: "jsx" 12 | }, 13 | server: { 14 | // Use VITE_PORT from your .env, or default to a port if not specified 15 | port: parseInt(process.env.VITE_PORT, 10) || 5173 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /App/backend/routes/peopleRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const { 4 | getPeople, 5 | getPersonByID, 6 | createPerson, 7 | updatePerson, 8 | deletePerson, 9 | } = require("../controllers/peopleController"); 10 | 11 | router.get("/", getPeople); 12 | router.get("/:id", getPersonByID); 13 | router.post("/", createPerson); 14 | router.put("/:id", updatePerson); 15 | router.delete("/:id", deletePerson); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /App/frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import { Routes, Route } from "react-router-dom"; 3 | import HomePage from "./pages/HomePage"; 4 | import PeoplePage from "./pages/PeoplePage"; 5 | import Navbar from "./components/navbar/NavBar"; 6 | 7 | function App() { 8 | return ( 9 | <> 10 | 11 | 12 | } /> 13 | } /> 14 | 15 | 16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /App/frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /App/backend/database/config.js: -------------------------------------------------------------------------------- 1 | // Get an instance of mysql we can use in the app 2 | const mysql = require("mysql2"); 3 | require("dotenv").config(); 4 | 5 | // Create a 'connection pool' using the provided credentials 6 | const pool = mysql.createPool({ 7 | connectionLimit: 10, 8 | waitForConnections: true, 9 | host: process.env.DB_HOST || "localhost", 10 | user: process.env.DB_USER || "root", 11 | password: process.env.DB_PASSWORD || "your_default_password", 12 | database: process.env.DB_DATABASE || "your_default_database", 13 | }).promise(); 14 | 15 | // Export it for use in our application 16 | module.exports = pool; 17 | -------------------------------------------------------------------------------- /App/frontend/src/components/navbar/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import { MdLocalConvenienceStore } from "react-icons/md"; 3 | 4 | const Navbar = () => { 5 | return ( 6 |
7 |
8 | 9 | 10 | 11 |
12 |

My website name

13 | 23 |
24 | ); 25 | }; 26 | 27 | export default Navbar; 28 | -------------------------------------------------------------------------------- /App/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cs340-react-starter-app-backend", 3 | "version": "1.0.0", 4 | "description": "This is the backend express API that connects the frontend to the mariadb database", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "nodemon server.js", 8 | "serve": "npx forever start server.js" 9 | }, 10 | "contributors": [ 11 | "Devin Daniels ", 12 | "Zac Maes " 13 | ], 14 | "license": "ISC", 15 | "dependencies": { 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.3.1", 18 | "express": "^4.18.2", 19 | "forever": "^4.0.3", 20 | "lodash": "^4.17.21", 21 | "mysql": "^2.18.1", 22 | "mysql2": "^3.5.2", 23 | "nodemon": "^3.0.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /App/frontend/src/pages/PeoplePage.jsx: -------------------------------------------------------------------------------- 1 | import { Routes, Route, Link } from "react-router-dom"; 2 | import CreatePerson from "../components/bsg_people/CreatePerson"; 3 | import PeopleTable from "../components/bsg_people/PersonTable"; 4 | import UpdatePerson from "../components/bsg_people/UpdatePerson"; 5 | 6 | function PeoplePage() { 7 | return ( 8 |
9 |

BSG People Page

10 | 20 | 21 | } /> 22 | } /> 23 | } /> 24 | 25 |
26 | ); 27 | } 28 | 29 | export default PeoplePage; 30 | -------------------------------------------------------------------------------- /App/backend/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | require("dotenv").config(); 4 | 5 | const app = express(); 6 | const PORT = process.env.PORT || 8500; 7 | 8 | // Middleware: 9 | 10 | // If on FLIP, use cors() middleware to allow cross-origin requests from the frontend with your port number: 11 | // EX (local): http://localhost:5173 12 | // EX (FLIP/classwork) http://flip3.engr.oregonstate.edu:5173 13 | app.use(cors({ credentials: true, origin: "*" })); 14 | app.use(express.json()); 15 | 16 | // API Routes for backend CRUD: 17 | app.use("/api/people", require("./routes/peopleRoutes")); 18 | 19 | 20 | // Add your Connect DB Activitiy Code Below: 21 | // ... 22 | 23 | 24 | // ... 25 | // End Connect DB Activity Code. 26 | 27 | 28 | const os = require("os"); 29 | const hostname = os.hostname(); 30 | 31 | app.listen(PORT, () => { 32 | // flip server should automatically match whatever server you're on 33 | console.log(`Server running: http://${hostname}:${PORT}...`); 34 | }); 35 | -------------------------------------------------------------------------------- /App/frontend/reactServer.cjs: -------------------------------------------------------------------------------- 1 | // reactServer.cjs 2 | // Uses common javascript to serve the react build folder (/dist) 3 | 4 | const express = require('express'); 5 | const path = require('path'); 6 | const app = express(); 7 | require("dotenv").config(); 8 | 9 | // Use the custom 'REACT_SERVER_PORT' port from .env, with a fallback to 3001 10 | const PORT = process.env.REACT_SERVER_PORT || 3001; 11 | 12 | // Serve the static files from the React app located in the build folder '/dist' 13 | // React router will take over frontend routing 14 | app.use(express.static(path.join(__dirname, 'dist'))); 15 | 16 | // Handles any requests that don't match the ones above to return the React app 17 | // A request to '/nonExist' will redirect to the index.html where react router takes over at '/' 18 | app.get('*', (req, res) => { 19 | res.sendFile(path.resolve(__dirname, 'dist', 'index.html')); 20 | }); 21 | 22 | app.listen(PORT, () => { 23 | // Change this text to whatever FLIP server you're on 24 | console.log(`Server running: http://flip3.engr.oregonstate.edu:${PORT}...`); 25 | }); -------------------------------------------------------------------------------- /App/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cs340-react-starter-app-frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "start": "vite", 8 | "build": "vite build", 9 | "serve": "npx forever start reactServer.cjs", 10 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview" 12 | }, 13 | "contributors": [ 14 | "Devin Daniels ", 15 | "Zac Maes " 16 | ], 17 | "dependencies": { 18 | "axios": "^1.6.3", 19 | "dotenv": "^16.4.5", 20 | "express": "^4.18.3", 21 | "forever": "^4.0.3", 22 | "path": "^0.12.7", 23 | "react": "^18.2.0", 24 | "react-dom": "^18.2.0", 25 | "react-icons": "^4.12.0", 26 | "react-router-dom": "^6.21.1" 27 | }, 28 | "devDependencies": { 29 | "@types/react": "^18.2.43", 30 | "@types/react-dom": "^18.2.17", 31 | "@vitejs/plugin-react": "^4.2.1", 32 | "eslint": "^8.55.0", 33 | "eslint-plugin-react": "^7.33.2", 34 | "eslint-plugin-react-hooks": "^4.6.0", 35 | "eslint-plugin-react-refresh": "^0.4.5", 36 | "vite": "^5.0.8" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /App/frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /App/frontend/src/components/bsg_people/PersonTable.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { RiCreativeCommonsZeroFill } from "react-icons/ri"; 3 | import TableRow from "./PersonTableRow"; 4 | import axios from "axios"; 5 | 6 | const PeopleTable = () => { 7 | const [people, setPeople] = useState([]); 8 | 9 | const fetchPeople = async () => { 10 | try { 11 | const URL = import.meta.env.VITE_API_URL + "people"; 12 | const response = await axios.get(URL); 13 | setPeople(response.data); 14 | } catch (error) { 15 | alert("Error fetching people from the server."); 16 | console.error("Error fetching people:", error); 17 | } 18 | }; 19 | 20 | useEffect(() => { 21 | fetchPeople(); 22 | }, []); 23 | 24 | return ( 25 |
26 |

BSG Person Table

27 | {people.length === 0 ? ( 28 |
29 | 30 |

No people on bsg found.

31 |
32 | ) : ( 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {people.map((person) => ( 47 | 48 | ))} 49 | 50 |
Person IDFirst NameLast NameHomeworldAgeEditDelete
51 | )} 52 |
53 | ); 54 | }; 55 | 56 | export default PeopleTable; 57 | -------------------------------------------------------------------------------- /App/frontend/src/components/bsg_people/PersonTableRow.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { BsTrash } from "react-icons/bs"; 3 | import { BiEditAlt } from "react-icons/bi"; 4 | import { useNavigate } from "react-router-dom"; 5 | 6 | /* eslint-disable react/prop-types */ 7 | const TableRow = ({ person, fetchPeople }) => { 8 | // Hook that allows us to navigate programmatically 9 | const navigate = useNavigate(); 10 | // Redirect to edit person page 11 | const handleEdit = () => { 12 | // We can access the id (and query the person) with useParams() in the UpdatePerson component 13 | navigate("/people/edit/" + person.id, { state: { person } }); 14 | }; 15 | 16 | const deleteRow = async () => { 17 | try { 18 | const URL = import.meta.env.VITE_API_URL + "people/" + person.id; 19 | const response = await axios.delete(URL); 20 | // Ensure that the person was deleted successfully 21 | if (response.status === 204) { 22 | alert("Person deleted successfully"); 23 | } 24 | } catch (err) { 25 | alert(err.response.data.error || "Error deleting person"); 26 | console.log(err); 27 | } 28 | fetchPeople(); 29 | }; 30 | 31 | return ( 32 | 33 | {person.id} 34 | {person.fname} 35 | {person.lname} 36 | {person.homeworld} 37 | {person.age} 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ); 46 | }; 47 | 48 | export default TableRow; 49 | -------------------------------------------------------------------------------- /App/frontend/src/components/bsg_people/CreatePerson.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import axios from "axios"; 4 | 5 | function CreatePerson() { 6 | const navigate = useNavigate(); 7 | 8 | const [formData, setFormData] = useState({ 9 | fname: "", 10 | lname: "", 11 | homeworld: "", 12 | age: "", 13 | }); 14 | 15 | const handleSubmit = async (e) => { 16 | // Prevent page reload 17 | e.preventDefault(); 18 | // Create a new person object from the formData 19 | const newPerson = { 20 | fname: formData.fname, 21 | lname: formData.lname, 22 | homeworld: formData.homeworld, 23 | age: formData.age, 24 | }; 25 | 26 | try { 27 | const URL = import.meta.env.VITE_API_URL + "people"; 28 | const response = await axios.post(URL, newPerson); 29 | if (response.status === 201) { 30 | navigate("/people"); 31 | } else { 32 | alert("Error creating person"); 33 | } 34 | } catch (error) { 35 | alert("Error creating person"); 36 | console.error("Error creating person:", error); 37 | } 38 | // Reset the form fields 39 | resetFormFields(); 40 | }; 41 | 42 | const resetFormFields = () => { 43 | setFormData({ 44 | fname: "", 45 | lname: "", 46 | homeworld: "", 47 | age: "", 48 | }); 49 | }; 50 | 51 | const handleInputChange = (e) => { 52 | const { name, value } = e.target; 53 | setFormData((prevData) => ({ 54 | ...prevData, 55 | [name]: value, 56 | })); 57 | }; 58 | 59 | return ( 60 | <> 61 |

Create BSG Person

62 |
63 | 64 | 70 | 71 | 77 | 78 | 84 | 85 | 86 | 87 |
88 | 89 | ); 90 | } 91 | 92 | export default CreatePerson; 93 | -------------------------------------------------------------------------------- /App/frontend/src/components/bsg_people/UpdatePerson.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useParams, useNavigate } from "react-router-dom"; 3 | import axios from "axios"; 4 | import { useLocation } from "react-router-dom"; 5 | 6 | const UpdatePerson = () => { 7 | const { id } = useParams(); 8 | const navigate = useNavigate(); 9 | const location = useLocation(); 10 | const prevPerson = location.state.person; 11 | 12 | const [formData, setFormData] = useState({ 13 | fname: prevPerson.fname || '', 14 | lname: prevPerson.lname || '', 15 | homeworld: prevPerson.homeworld || '', 16 | age: prevPerson.age || '', 17 | }); 18 | 19 | const handleInputChange = (event) => { 20 | const { name, value } = event.target; 21 | setFormData((prevFormData) => ({ 22 | ...prevFormData, 23 | [name]: value, 24 | })); 25 | }; 26 | 27 | function isUpdate(){ 28 | // Check if formData is equal to prevPerson 29 | if (JSON.stringify(formData) === JSON.stringify({ 30 | fname: prevPerson.fname || '', 31 | lname: prevPerson.lname || '', 32 | homeworld: prevPerson.homeworld || '', 33 | age: prevPerson.age || '', 34 | })) { 35 | alert("No changes made."); 36 | return false; 37 | } 38 | return true 39 | } 40 | 41 | const handleSubmit = async (event) => { 42 | // Stop default form behavior which is to reload the page 43 | event.preventDefault(); 44 | // Check if formData is equal to prevPerson 45 | if (isUpdate()){ 46 | try { 47 | const URL = import.meta.env.VITE_API_URL + "people/" + id; 48 | const response = await axios.put(URL, formData); 49 | if (response.status !== 200) { 50 | alert("Error updating person"); 51 | } else { 52 | alert(response.data.message); 53 | // Redirect to people page 54 | navigate("/people"); 55 | } 56 | } catch (err) { 57 | console.log("Error updating person:", err); 58 | } 59 | } 60 | }; 61 | 62 | return ( 63 |
64 |

Update Person

65 |
66 |
67 | 68 | 75 |
76 |
77 | 78 | 85 |
86 |
87 | 88 | 94 |
95 |
96 | 97 | 104 |
105 | 108 | 109 |
110 |
111 | ); 112 | }; 113 | 114 | export default UpdatePerson; 115 | 116 | -------------------------------------------------------------------------------- /App/backend/database/ddl.sql: -------------------------------------------------------------------------------- 1 | -- Disable foreign key checks temporarily 2 | SET 3 | foreign_key_checks = 0; 4 | 5 | -- Planets table 6 | DROP TABLE IF EXISTS `bsg_planets`; 7 | 8 | CREATE TABLE `bsg_planets` ( 9 | `id` INT(11) NOT NULL AUTO_INCREMENT, 10 | `name` VARCHAR(255) NOT NULL, 11 | `population` BIGINT(20) DEFAULT NULL, 12 | `language` VARCHAR(255) DEFAULT NULL, 13 | `capital` VARCHAR(255) DEFAULT NULL, 14 | PRIMARY KEY (`id`), 15 | UNIQUE KEY `name` (`name`) 16 | ); 17 | 18 | INSERT INTO 19 | `bsg_planets` ( 20 | `id`, 21 | `name`, 22 | `population`, 23 | `language`, 24 | `capital` 25 | ) 26 | VALUES 27 | ( 28 | 1, 29 | 'Gemenon', 30 | 2800000000, 31 | 'Old Gemenese', 32 | 'Oranu' 33 | ), 34 | (2, 'Leonis', 2600000000, 'Leonese', 'Luminere'), 35 | ( 36 | 3, 37 | 'Caprica', 38 | 4900000000, 39 | 'Caprican', 40 | 'Caprica City' 41 | ), 42 | (7, 'Sagittaron', 1700000000, NULL, 'Tawa'), 43 | (16, 'Aquaria', 25000, NULL, NULL), 44 | (17, 'Canceron', 6700000000, NULL, 'Hades'), 45 | (18, 'Libran', 2100000, NULL, NULL), 46 | (19, 'Picon', 1400000000, NULL, 'Queestown'), 47 | (20, 'Scorpia', 450000000, NULL, 'Celeste'), 48 | (21, 'Tauron', 2500000000, 'Tauron', 'Hypatia'), 49 | (22, 'Virgon', 4300000000, NULL, 'Boskirk'); 50 | 51 | -- Certification table 52 | DROP TABLE IF EXISTS `bsg_cert`; 53 | 54 | CREATE TABLE `bsg_cert` ( 55 | `id` INT(11) NOT NULL AUTO_INCREMENT, 56 | `title` VARCHAR(255) NOT NULL, 57 | PRIMARY KEY (`id`) 58 | ); 59 | 60 | INSERT INTO 61 | `bsg_cert` (`id`, `title`) 62 | VALUES 63 | (1, 'Raptor'), 64 | (2, 'Viper'), 65 | (3, 'Mechanic'), 66 | (4, 'Command'); 67 | 68 | -- People table 69 | DROP TABLE IF EXISTS `bsg_people`; 70 | 71 | CREATE TABLE `bsg_people` ( 72 | `id` INT(11) NOT NULL AUTO_INCREMENT, 73 | `fname` VARCHAR(255) NOT NULL, 74 | `lname` VARCHAR(255) DEFAULT NULL, 75 | `homeworld` INT(11) DEFAULT NULL, 76 | `age` INT(11) DEFAULT NULL, 77 | PRIMARY KEY (`id`), 78 | KEY `homeworld` (`homeworld`), 79 | CONSTRAINT `bsg_people_ibfk_1` FOREIGN KEY (`homeworld`) REFERENCES `bsg_planets` (`id`) ON DELETE 80 | SET 81 | NULL ON UPDATE CASCADE 82 | ) ENGINE = InnoDB AUTO_INCREMENT = 10 DEFAULT CHARSET = latin1; 83 | 84 | INSERT INTO 85 | `bsg_people` (`id`, `fname`, `lname`, `homeworld`, `age`) 86 | VALUES 87 | (1, 'William', 'Adama', 3, 61), 88 | (2, 'Lee', 'Adama', 3, 30), 89 | (3, 'Laura', 'Roslin', 3, NULL), 90 | (4, 'Kara', 'Thrace', 3, NULL), 91 | (5, 'Gaius', 'Baltar', 3, NULL), 92 | (6, 'Saul', 'Tigh', NULL, 71), 93 | (7, 'Karl', 'Agathon', 1, NULL), 94 | (8, 'Galen', 'Tyrol', 1, 32), 95 | (9, 'Callandra', 'Henderson', NULL, NULL); 96 | 97 | -- Certification-People relationship table 98 | CREATE TABLE `bsg_cert_people` ( 99 | `cid` INT(11) NOT NULL DEFAULT '0', 100 | `pid` INT(11) NOT NULL DEFAULT '0', 101 | PRIMARY KEY (`cid`, `pid`), 102 | KEY `pid` (`pid`), 103 | CONSTRAINT `bsg_cert_people_ibfk_1` FOREIGN KEY (`cid`) REFERENCES `bsg_cert` (`id`), 104 | CONSTRAINT `bsg_cert_people_ibfk_2` FOREIGN KEY (`pid`) REFERENCES `bsg_people` (`id`) 105 | ); 106 | 107 | INSERT INTO 108 | `bsg_cert_people` (`cid`, `pid`) 109 | VALUES 110 | (2, 2), 111 | (4, 2), 112 | (4, 3), 113 | (2, 4), 114 | (4, 6), 115 | (1, 7), 116 | (3, 8), 117 | (3, 9); 118 | 119 | -- Re-enable foreign key checks 120 | SET 121 | foreign_key_checks = 1; -------------------------------------------------------------------------------- /App/frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /App/backend/controllers/peopleController.js: -------------------------------------------------------------------------------- 1 | // Load db config 2 | const db = require("../database/config"); 3 | // Load .env variables 4 | require("dotenv").config(); 5 | // Util to deep-compare two objects 6 | const lodash = require("lodash"); 7 | 8 | // Returns all rows of people in bsg_people 9 | const getPeople = async (req, res) => { 10 | try { 11 | // Select all rows from the "bsg_people" table 12 | const query = "SELECT * FROM bsg_people"; 13 | // Execute the query using the "db" object from the configuration file 14 | const [rows] = await db.query(query); 15 | // Send back the rows to the client 16 | res.status(200).json(rows); 17 | } catch (error) { 18 | console.error("Error fetching people from the database:", error); 19 | res.status(500).json({ error: "Error fetching people" }); 20 | } 21 | }; 22 | 23 | // Returns a single person by their unique ID from bsg_people 24 | const getPersonByID = async (req, res) => { 25 | try { 26 | const personID = req.params.id; 27 | const query = "SELECT * FROM bsg_people WHERE id = ?"; 28 | const [result] = await db.query(query, [personID]); 29 | // Check if person was found 30 | if (result.length === 0) { 31 | return res.status(404).json({ error: "Person not found" }); 32 | } 33 | const person = result[0]; 34 | res.json(person); 35 | } catch (error) { 36 | console.error("Error fetching person from the database:", error); 37 | res.status(500).json({ error: "Error fetching person" }); 38 | } 39 | }; 40 | 41 | // Returns status of creation of new person in bsg_people 42 | const createPerson = async (req, res) => { 43 | try { 44 | const { fname, lname, homeworld, age } = req.body; 45 | const query = 46 | "INSERT INTO bsg_people (fname, lname, homeworld, age) VALUES (?, ?, ?, ?)"; 47 | 48 | const response = await db.query(query, [ 49 | fname, 50 | lname, 51 | homeworld === "" ? null : parseInt(homeworld), 52 | age, 53 | ]); 54 | res.status(201).json(response); 55 | } catch (error) { 56 | // Print the error for the dev 57 | console.error("Error creating person:", error); 58 | // Inform the client of the error 59 | res.status(500).json({ error: "Error creating person" }); 60 | } 61 | }; 62 | 63 | 64 | const updatePerson = async (req, res) => { 65 | // Get the person ID 66 | const personID = req.params.id; 67 | // Get the person object 68 | const newPerson = req.body; 69 | 70 | try { 71 | const [data] = await db.query("SELECT * FROM bsg_people WHERE id = ?", [ 72 | personID, 73 | ]); 74 | 75 | const oldPerson = data[0]; 76 | 77 | // If any attributes are not equal, perform update 78 | if (!lodash.isEqual(newPerson, oldPerson)) { 79 | const query = 80 | "UPDATE bsg_people SET fname=?, lname=?, homeworld=?, age=? WHERE id=?"; 81 | 82 | // Homeoworld is NULL-able FK in bsg_people, has to be valid INT FK ID or NULL 83 | const hw = newPerson.homeworld === "" ? null : newPerson.homeworld; 84 | 85 | const values = [ 86 | newPerson.fname, 87 | newPerson.lname, 88 | hw, 89 | newPerson.age, 90 | personID, 91 | ]; 92 | 93 | // Perform the update 94 | await db.query(query, values); 95 | // Inform client of success and return 96 | return res.json({ message: "Person updated successfully." }); 97 | } 98 | 99 | res.json({ message: "Person details are the same, no update" }); 100 | } catch (error) { 101 | console.log("Error updating person", error); 102 | res 103 | .status(500) 104 | .json({ error: `Error updating the person with id ${personID}` }); 105 | } 106 | }; 107 | 108 | // Endpoint to delete a customer from the database 109 | const deletePerson = async (req, res) => { 110 | console.log("Deleting person with id:", req.params.id); 111 | const personID = req.params.id; 112 | 113 | try { 114 | // Ensure the person exitst 115 | const [isExisting] = await db.query( 116 | "SELECT 1 FROM bsg_people WHERE id = ?", 117 | [personID] 118 | ); 119 | 120 | // If the person doesn't exist, return an error 121 | if (isExisting.length === 0) { 122 | return res.status(404).send("Person not found"); 123 | } 124 | 125 | // Delete related records from the intersection table (see FK contraints bsg_cert_people) 126 | const [response] = await db.query( 127 | "DELETE FROM bsg_cert_people WHERE pid = ?", 128 | [personID] 129 | ); 130 | 131 | console.log( 132 | "Deleted", 133 | response.affectedRows, 134 | "rows from bsg_cert_people intersection table" 135 | ); 136 | 137 | // Delete the person from bsg_people 138 | await db.query("DELETE FROM bsg_people WHERE id = ?", [personID]); 139 | 140 | // Return the appropriate status code 141 | res.status(204).json({ message: "Person deleted successfully" }) 142 | } catch (error) { 143 | console.error("Error deleting person from the database:", error); 144 | res.status(500).json({ error: error.message }); 145 | } 146 | }; 147 | 148 | // Export the functions as methods of an object 149 | module.exports = { 150 | getPeople, 151 | getPersonByID, 152 | createPerson, 153 | updatePerson, 154 | deletePerson, 155 | }; 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS 340 React Starter Guide 2 | 3 | 16 | 17 | 18 | ## Table of Contents 19 | 1. [Contributions](#contributions) 20 | 2. [TLDR](#tldr) 21 | 3. [Overview](#overview) 22 | 4. [Resources](#resources) 23 | 5. [Getting Started](#getting-started) 24 | 6. [Backend Setup (Node.js/Express)](#backend-setup-nodejsexpress) 25 | 7. [Frontend Setup (Vite)](#frontend-setup-vite) 26 | 8. [React and Node.js Assignment - Connecting to a MySQL Database](#react-and-nodejs-assignment---connecting-to-a-mysql-database) 27 | 9. [Understanding Terminal Commands and NPM Scripts](#understanding-terminal-commands-and-npm-scripts) 28 | 10. [Build and Deploy](#build-and-deploy) 29 | 11. [Screenshots of Build and API Served With Forever](#build-and-api-served-with-forever) 30 | 31 | ## Contributions 32 | 33 | This guide was developed by [Devin Daniels](https://github.com/devingdaniels) and [Zachary Maes](https://github.com/zacmaes) under the supervision of [Dr. Michael Curry](mailto:michael.curry@oregonstate.edu) and [Dr. Danielle Safonte](mailto:danielle.safonte@oregonstate.edu). 34 | 35 | 36 | ## TLDR 37 | Assuming you are ssh logged into flip or classwork osu servers. To view the application you must be logged into the osu vpn service. 38 | 1. Clone the starter app repository: `git clone git@github.com:osu-cs340-ecampus/react-starter-app.git` 39 | - Follow the git guide on canvas, you may need a github personal access token 40 | 41 | 2. Navigate to the `/App` directory: `cd react-starter-app/App` 42 | 43 | 3. Set up the backend: 44 | - Change directory to /backend/database: `cd ../backend/database` 45 | - Login to MariaDB in terminal 46 | - In Mariadb run `source ddl.sql;`, and confirm the database is added without error. Then exit Mariadb. 47 | - Change directory to /backend: `cd ..` 48 | - Update all of your .env information locally, see [Backend Setup (Node.js/Express)](#backend-setup-nodejsexpress) 49 | - Install necessary packages: `npm install` 50 | - Launch the backend: `npm start` 51 | 52 | 4. Set up the frontend: 53 | - Change directory to frontend: `cd frontend` 54 | - Update all of your .env information locally, see [Frontend Setup (Vite)](#frontend-setup-vite) 55 | - Install necessary packages: `npm install` 56 | - Launch the frontend: `npm start` 57 | 58 | 5. Begin your development journey: Happy Hacking! 59 | 60 | ## Overview 61 | 62 | This guide is tailored for students enrolled in CS 340 who aim to develop their final project using React.js, Node/Express, and MySQL. The goal of this repo is to provide you with the basic structure of a React/Vite + Express/Node full-stack application, including a few example SQL queries. 63 | 64 | The starter code provided in this guide encompasses essential components such as tool setup, infrastructure for building and running your application, and guidelines for deploying your application to OSU's Flip server. 65 | 66 | Key Assumptions: 67 | 68 | 1. You have read through and understand the [nodejs-starter-app](https://github.com/osu-cs340-ecampus/nodejs-starter-app) 69 | - That guide uses nodejs, express, and handlebars, but goes deeper into the inner workings of express and nodejs. 70 | 2. You have a foundational understanding of JavaScript and MySQL syntax. 71 | 3. You are adept at using terminal commands like `cd`, `ls`, `mkdir`, etc. 72 | 4. Access to OSU's flip servers and a MySQL database is available to you. 73 | - Note: Adaptations for local development are possible and outlined in this guide. 74 | 5. You have some familiarity with react hooks like `useState()` and `useEffect()`. 75 | - The code in this guide uses these hooks for state management and network requests. Examples of these are provided and described, but you may need to do more research for a better understanding of how they work. 76 | 77 | ## Resources 78 | - [Vite Docs](https://vitejs.dev/guide/) 79 | - [New React Docs](https://react.dev/) 80 | - [Old React Docs](https://legacy.reactjs.org/docs/getting-started.html) 81 | - [Express Docs](https://expressjs.com/) 82 | 83 | - [Video - useState Hook](https://youtu.be/O6P86uwfdR0?si=dEIYE2SZFrsaFenH) 84 | - [Video - useEffect Hook](https://youtu.be/0ZJgIjIuY7U?si=cUOxarBpQvSnswT9) 85 | - [Video - Serving build](https://youtu.be/4pUBO31nkpk?si=C9tFJbMi-QP31IJm) 86 | - **Videos - CRUD in React** 87 | 1. [Part 1](https://youtu.be/T8mqZZ0r-RA?si=s2dJnArkHCkvoZml) 88 | 2. [Part 2](https://youtu.be/3YrOOia3-mo?si=ZNYcD6Ee9KemtNFu) 89 | 3. [Part 3](https://youtu.be/_S2GKnFpdtE?si=P8QYLMORSslHmsLn) 90 | 91 | 92 | ## Getting Started 93 | 94 | IDE: Visual Studio Code 95 | 96 | Browser: Chrome or Firefox are recommended, though other browsers will probably suffice. 97 | 98 | VScode SSH extension: [Visual Studio Code SSH Documentation](https://code.visualstudio.com/docs/remote/ssh) 99 | 100 | Terminal: The built-in terminal in VSCode works great. 101 | 102 | Flip Server: flip 1-4 or classwork servers and ports associated with those [1024 < PORT < 65535] 103 | 104 | OSU VPN: Access to vpn services (see canvas) required for viewing links on flip. 105 | 106 | ## Backend Setup (Node.js/Express) 107 | 108 | 1. Create a `.env` file in the root directory (`.env` should have the same indentation as `server.js`, both need to be at the root of the directory). 109 | 2. Git cloning does not include the `.env` file for obvious reasons. Here is the naming schema I would suggest (fill in with info from activity 2 - Connecting to 340 DB)): 110 | ```python 111 | DB_HOST="classmysql.engr.oregonstate.edu" # keep this 112 | DB_USER="cs340_youronid" # replace with your onid 113 | DB_DATABASE="cs340_youronid" # replace with your onid 114 | DB_PASSWORD="****" # your db password - last 4 digits of osu id number 115 | PORT=8500 # Set a port number between:[1024 < PORT < 65535] 116 | 3. `server.js` is the entry point for the backend. No changes are needed here except perhaps updating the `console.log()` statement in the `app.listen()` to reflect the FLIP server you've connected to via SSH. 117 | 118 | 4. Inside your flip server you will need to set up the `ddl.sql` file located inside `/backend/databases/` using the source command. 119 | ```sh 120 | # change directory to where the ddl.sql file is located 121 | cd react-starter-app/App/backend/databases # or wherever the ddl.sql is located 122 | 123 | # normal login command (if not using shortcut) 124 | mysql -u cs340_youronid -h classmysql.engr.oregonstate.edu -p cs340_youronid 125 | 126 | # type in your 4 digit mariadb password and press 'enter' key 127 | ``` 128 | 129 | flip will login to mariadb... 130 | ``` 131 | Welcome to the MariaDB monitor. Commands end with ; or \g. 132 | Your MariaDB connection id is 1359790 133 | Server version: 10.6.16-MariaDB-log MariaDB Server 134 | 135 | Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. 136 | 137 | Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 138 | ``` 139 | Use 'show tables;' command to ensure that your database is empty, backup tables if necessary prior to moving on, drop all of your tables. 140 | ```sql 141 | MariaDB [cs340_maesz]> SHOW tables; 142 | Empty set (0.001 sec) 143 | ``` 144 | Now you can source the ddl.sql 145 | ```sql 146 | MariaDB [cs340_maesz]> source ddl.sql; 147 | Query OK, 0 rows affected (0.001 sec) 148 | 149 | Query OK, 0 rows affected, 1 warning (0.004 sec) 150 | 151 | Query OK, 0 rows affected (0.007 sec) 152 | 153 | Query OK, 11 rows affected (0.001 sec) 154 | Records: 11 Duplicates: 0 Warnings: 0 155 | 156 | Query OK, 0 rows affected, 1 warning (0.003 sec) 157 | 158 | Query OK, 0 rows affected (0.005 sec) 159 | 160 | Query OK, 4 rows affected (0.000 sec) 161 | Records: 4 Duplicates: 0 Warnings: 0 162 | 163 | Query OK, 0 rows affected, 1 warning (0.003 sec) 164 | 165 | Query OK, 0 rows affected (0.006 sec) 166 | 167 | Query OK, 9 rows affected (0.001 sec) 168 | Records: 9 Duplicates: 0 Warnings: 0 169 | 170 | Query OK, 0 rows affected (0.007 sec) 171 | 172 | Query OK, 8 rows affected (0.001 sec) 173 | Records: 8 Duplicates: 0 Warnings: 0 174 | 175 | Query OK, 0 rows affected (0.000 sec) 176 | ``` 177 | Confirm everything sourced with a `show tables;` command 178 | ```sql 179 | MariaDB [cs340_maesz]> show tables; 180 | +-----------------------+ 181 | | Tables_in_cs340_maesz | 182 | +-----------------------+ 183 | | bsg_cert | 184 | | bsg_cert_people | 185 | | bsg_people | 186 | | bsg_planets | 187 | +-----------------------+ 188 | 4 rows in set (0.001 sec) 189 | ``` 190 | Exit mariadb 191 | ```sh 192 | MariaDB [cs340_maesz]> exit 193 | Bye 194 | flip3 ~/react-starter-app/App/backend/database 1010$ # We are now back in the terminal 195 | ``` 196 | 197 | 5. Now you must install all the node dependencies outlined in the `package.json` and `package-lock.json`. Run the following commands to do this: 198 | 199 | Change directory to `/backend`, wherever that exists in your file structure. 200 | ```sh 201 | cd ~/react-starter-app/App/backend 202 | ``` 203 | Use `npm install` to download everything (Some of you may have to debug this step if anything goes wrong...): 204 | ```sh 205 | flip3 ~/react-starter-app/App/backend 1023$ npm install 206 | 207 | # installer does some magic... 208 | 209 | added 122 packages, and audited 123 packages in 28s 210 | 211 | 15 packages are looking for funding 212 | run `npm fund` for details 213 | 214 | found 0 vulnerabilities 215 | flip3 ~/react-starter-app/App/backend 1024$ ▌ 216 | ``` 217 | 218 | 6. Now you can start your application with the start script located in the `package.json` 219 | ```bash 220 | flip3 ~/react-starter-app/App/backend 1024$ npm start 221 | 222 | > backend@1.0.0 start 223 | > nodemon server.js 224 | 225 | [nodemon] 3.0.2 226 | [nodemon] to restart at any time, enter `rs` 227 | [nodemon] watching path(s): *.* 228 | [nodemon] watching extensions: js,mjs,cjs,json 229 | [nodemon] starting `node server.js` 230 | Server running: http://flip3.engr.oregonstate.edu:8500... 231 | ▌ 232 | ``` 233 | 234 | This repo uses the package `nodemon` to run your program continuously. You may also install the package `forever` to accomplish this, see the [nodejs-starter-app](https://github.com/osu-cs340-ecampus/nodejs-starter-app) for instructions regarding the `forever` package. 235 | 236 | Remember, you must change the port numbers! 237 | 238 | ## Frontend Setup (Vite) 239 | 240 | This section will guide you through setting up the frontend part of your application using Vite and React. Create React App has traditionally been the go to way to develop a react project, but It has been deprecated (no longer being updated or has support). Vite is the more modern bundling solution, which is what we use in this project. It is highly recomended that you read the [documentation for Vite](https://vitejs.dev/guide/) to understand how it works. It is very similar to CRA so many tutorials for CRA are still appicable to Vite projects for when you are searching the web for help. 241 | 242 | Vite allows you to write react code, start a development server with `npm start`, and build a static `/dist` folder to serve your finished application (more on this in a later section). The scripts for these can be found or modified inside the `package.json`. Now we will walk through how to get your development server set up and running. 243 | 244 | 245 | 1. Navigate to the `/frontend` directory. 246 | ```sh 247 | flip3 ~/react-starter-app/App 1006$ cd frontend 248 | ``` 249 | 250 | 2. Modify the fronted `.env` file so that `VITE_API_URL` matches the backend api url you created in the prior steps. Also modify the frontend `VITE_PORT` for your dev server to run on. We will cover the 251 | ```python 252 | # Vite environment variables 253 | 254 | # Note: Every team member should have their own version of this file locally. Do not commit this to github 255 | # in real world development scenarios, this may contain sensitve keys, that is why this is listed on .gitignore 256 | 257 | # PLEASE CHANGE THE PORTS TO YOUR LIKING ... between:[1024 < PORT < 65535] 258 | # Remember: only one person can use a port at a time 259 | 260 | # This is the port used for your vite development server with `npm start` or `npm run dev`. That is configured in the the file vite.config.js 261 | # When you run `npm start` or `npm run dev` you can access your website on port 8501. Change this to your liking. 262 | VITE_PORT=8501 # Set a port number between:[1024 < PORT < 65535], this should NOT be the same as the API port. 263 | 264 | # This is the port used in the /frontend/reactServer.js to host your '/dist' build folder after running 'npm build', change this to your liking. 265 | REACT_SERVER_PORT=6061 # This is the port used in the /frontend/reactServer.js to host your '/dist' build folder... more on this later in the guide... 266 | 267 | # This is the url that points to your 'backend/server.js' and you must change the flip number and port to match what you set up there. 268 | # This url is where your SQL server code recieves and sends CRUD operations. 269 | VITE_API_URL='http://flip3.engr.oregonstate.edu:8500/api/' # Change this url to match your backend express api url and port. 270 | ``` 271 | 272 | The `VITE_API_URL` environment variable is used to fetch data from the backend api to this frontend application with axios in components like `PersonTable.jsx`. Here is a function from that file to demonstrate this: 273 | ```jsx 274 | // FILE: PersonTable.jsx 275 | const fetchPeople = async () => { 276 | try { 277 | const URL = import.meta.env.VITE_API_URL + "people"; 278 | const response = await axios.get(URL); 279 | setPeople(response.data); 280 | } catch (error) { 281 | alert("Error fetching people from the server."); 282 | console.error("Error fetching people:", error); 283 | } 284 | }; 285 | ``` 286 | 287 | The `VITE_PORT` environment variable is used to modify the frontend port that this vite application runs on. This is set up inside the file `vite.config.js`. Usually the default port for vite react projects is `5173`, but we are using dotenv to modify this to a port of your choosing. You can see how this works below: 288 | 289 | ```js 290 | // FILE: vite.config.js 291 | import { defineConfig } from 'vite' 292 | import react from '@vitejs/plugin-react' 293 | import dotenv from 'dotenv' 294 | 295 | dotenv.config() 296 | 297 | // https://vitejs.dev/config/ 298 | export default defineConfig({ 299 | plugins: [react()], 300 | esbuild: { 301 | loader: "jsx" 302 | }, 303 | server: { 304 | // Use VITE_PORT from your .env, or default to a port if not specified 305 | port: parseInt(process.env.VITE_PORT, 10) || 5173 306 | } 307 | }) 308 | ``` 309 | 310 | 3. Install all the frontend dependencies. This will add the `node_modules` folder so that your project can run properly. 311 | ```sh 312 | flip3 ~/react-starter-app/App/frontend 1008$ npm install 313 | 314 | # installer does some magic... 315 | 316 | added 284 packages, and audited 285 packages in 17s 317 | 318 | 99 packages are looking for funding 319 | run `npm fund` for details 320 | 321 | found 0 vulnerabilities 322 | flip3 ~/react-starter-app/App/frontend 1008$ ▌ 323 | ``` 324 | 325 | 4. Now you are ready to start the application using the start script inside the `package.json`. When working with the Vite development server on a remote server (e.g., flip3), there are different ways to start the server and access your application, depending on whether you need local access (on the remote server itself) or external access (from your own computer or the internet). 326 | 327 | To accomplish these options, this guide assumes that you are following the instructions for SSH through Vscode using the [Remote - SSH](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) Vscode plugin. The instructions for setting up your ssh client can be found on the Canvas Practice Resources. 328 | 329 | With that being said, this magical VSCode plugin is able to auto-forward the ports from the remote flip server to your local machine. This means that when you start the development server on flip, VSCode can tunnel the server's port back to your local machine, so you can access it as if it were running locally via `localhost:PORT/` or `127.0.0.1:PORT/`. Please note that to view the URLs in both options below, you must be signed into the VPN. 330 | 331 | ### Option 1 - Local Only 332 | When you start the Vite server using the standard `npm run start` command, the server binds to `localhost` of the remote machine. The VSCode Remote - SSH plugin automatically detects this and sets up port forwarding, allowing you to access the server using `localhost` or `127.0.0.1` in your local machine's browser. This method keeps the server private to your local machine, and it cannot be viewed by anyone else except you. 333 | ```sh 334 | flip3 ~/react-starter-app/App/frontend 1003$ npm run start 335 | 336 | > cs340-react-starter-app@0.0.0 start 337 | > vite 338 | 339 | 340 | VITE v5.1.4 ready in 525 ms 341 | 342 | ➜ Local: http://127.0.0.1:8501/ or http://localhost:8501/ 343 | ➜ Network: use --host to expose 344 | ➜ press h + enter to show help 345 | 346 | h # use h to see some options 347 | 348 | Shortcuts 349 | press r + enter to restart the server 350 | press u + enter to show server url 351 | press o + enter to open in browser 352 | press c + enter to clear console 353 | press q + enter to quit 354 | 355 | q # Use q to stop vite from running and return to the terminal. 356 | 357 | flip3 ~/ula_cs340/winter24/react-starter-app/App/frontend 1003$ ▌ 358 | 359 | ``` 360 | 361 | ### Option 2 - Expose to Network 362 | If you need to share your development server with teammates or access it from devices other than your local machine, you can start the server with the `--host` option by running `npm start -- --host`. This tells Vite to listen on all network interfaces, making the server accessible via the remote server's public IP address. Note that VSCode will still forward this port, but now other devices can also access the server if they can reach the remote server's IP. 363 | 364 | ```sh 365 | flip3 ~/ula_cs340/winter24/react-starter-app/App/frontend 1003$ npm start -- --host 366 | 367 | > cs340-react-starter-app@0.0.0 start 368 | > vite "--host" 369 | 370 | 371 | VITE v5.1.4 ready in 1330 ms 372 | 373 | ➜ Local: http://localhost:8501/ or http://127.0.0.1:8501/ 374 | ➜ Network: http://128.193.36.41:8501/ or http://flip3.engr.oregonstate.edu:8501/ 375 | # Now anyone with the VPN can view your dev server at these network URLs 376 | ➜ press h + enter to show help 377 | 378 | h # use h to see some options 379 | 380 | Shortcuts 381 | press r + enter to restart the server 382 | press u + enter to show server url 383 | press o + enter to open in browser 384 | press c + enter to clear console 385 | press q + enter to quit 386 | 387 | q # Use q to stop vite from running and return to the terminal. 388 | 389 | flip3 ~/ula_cs340/winter24/react-starter-app/App/frontend 1004$ ▌ 390 | ``` 391 | 392 | ### Important Note About Stopping Processes! 393 | While testing the command `npm start -- --host`, it was observed that using `^C` to send the `SIGINT` signal does not always lead to a clean shutdown of the Vite development server. This is because `SIGINT` may not terminate child processes spawned by Vite, leading to the server process lingering in the background. 394 | 395 | If you encounter issues where the server's port remains in use even after attempting to stop the server with `^C`, you can follow these steps for a more forceful shutdown: 396 | 397 | 1. Identify the lingering process: 398 | - Use `lsof -i :` to list all processes using the port. 399 | - Alternatively, `ps -u $(whoami)` lists all your running processes, helping identify the PID of the node process for Vite. 400 | 401 | 2. Terminate the process: 402 | - Use `kill -9 ` to forcefully terminate the identified process. Replace `` with the actual process ID you found in the previous step. 403 | 404 | **Note:** Always attempt to gracefully shut down the server using the built-in quit command (if available) or the standard `^C` method first. Resort to `kill -9` only when necessary as it terminates processes abruptly, without allowing them to clean up resources or perform a graceful shutdown. I discovered this when I exited out of my ssh session and I was still able to access my application running on `http://flip3.engr.oregonstate.edu:8501/` even though I had used `^C`. When I logged back into flip via ssh, upon trying to run `npm start -- --host` or `npm start` Vite would automatically tell me that port `8501` was still in use, and then it would start a new server on port `8502`. 405 | 406 | ```sh 407 | # use this command to see what the PID of the process is that is running on a specific port. 408 | flip3 ~ 1009$ lsof -i :8501 409 | # Our runaway process is identified as 2502508 410 | COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME 411 | node 2502508 maesz 22u IPv6 357398260 0t0 TCP *:cmtp-mgt (LISTEN) 412 | node 2502508 maesz 29u IPv6 357423874 0t0 TCP flip3.engr.oregonstate.edu:cmtp-mgt->10-197-151-117.sds.oregonstate.edu:64081 (ESTABLISHED) 413 | node 2502508 maesz 30u IPv6 357944796 0t0 TCP flip3.engr.oregonstate.edu:cmtp-mgt->10-197-151-117.sds.oregonstate.edu:64620 (ESTABLISHED) 414 | 415 | # use this command to see all of your running processes 416 | flip3 ~ 1010$ ps -u $(whoami) 417 | PID TTY TIME CMD 418 | 2501520 ? 00:00:00 systemd 419 | 2501523 ? 00:00:00 (sd-pam) 420 | 2501554 ? 00:00:00 sshd 421 | 2501556 pts/393 00:00:00 bash 422 | 2502508 pts/393 00:00:03 node # This is our runaway process 2502508 423 | 2502541 pts/393 00:00:01 esbuild 424 | 2512357 ? 00:00:00 sh 425 | 2512428 ? 00:00:09 node 426 | 2513575 ? 00:00:04 node 427 | 2517189 ? 00:00:00 sshd 428 | 2517190 ? 00:00:00 bash 429 | 2517570 ? 00:00:00 bash 430 | 2517908 ? 00:00:18 node 431 | 2517920 ? 00:00:00 node 432 | 2518045 ? 00:00:04 node 433 | 2518267 ? 00:00:00 sshd 434 | 2518272 ? 00:00:00 bash 435 | 2518654 ? 00:00:00 bash 436 | 2518673 ? 00:00:00 code-903b1e9d89 437 | 2518719 ? 00:00:00 sh 438 | 2518721 pts/464 00:00:00 bash 439 | 2552243 pts/525 00:00:00 bash 440 | 2556916 ? 00:00:01 node 441 | 2574369 ? 00:00:00 sshd 442 | 2574370 pts/455 00:00:00 bash 443 | 2614401 ? 00:00:00 sleep 444 | 2615639 pts/455 00:00:00 ps 445 | 446 | # use this command to kill the process with the PID 447 | flip3 ~ 1011$ kill -9 2502508 448 | ``` 449 | 450 | 451 | ## React and Node.js Assignment - Connecting to a MySQL Database 452 | Now that your application is set up and running, this section will guide you through adding a diagnostic API endpoint to your Node.js Express backend and displaying its data on your React application's homepage. This integration will ensure your backend and database are correctly configured and communicating with your frontend. 453 | 454 | ### Step 1: Backend Setup 455 | First, ensure you have MySQL installed and set up in your environment. Then, install the MySQL package in your `/backend` directory if you haven't already with `npm install mysql --save`. 456 | 457 | ### Step 2: Create Diagnostic Route 458 | In your `/backend/server.js` file, import your database connector and define a new `GET` request route at `/api/diagnostic` that performs a series of database operations: 459 | ```javascript 460 | 461 | // Match to your database config route 462 | const db = require('./database/config.js'); 463 | 464 | // define a new GET request with express: 465 | app.get('/api/diagnostic', async (req, res) => { 466 | try { 467 | // Await your database queries here 468 | await db.pool.query('DROP TABLE IF EXISTS diagnostic;'); 469 | await db.pool.query('CREATE TABLE diagnostic(id INT PRIMARY KEY AUTO_INCREMENT, text VARCHAR(255) NOT NULL);'); 470 | await db.pool.query('INSERT INTO diagnostic (text) VALUES ("MySQL is working!")'); 471 | const results = await db.pool.query('SELECT * FROM diagnostic;'); 472 | 473 | // res.json() automatically stringifies the JavaScript object to JSON 474 | res.json(results); 475 | 476 | } catch (error) { 477 | // Handle Errors 478 | console.error('Database operation failed:', error); 479 | res.status(500).send('Server error'); 480 | } 481 | }); 482 | 483 | ``` 484 | 485 | ### Step 3: Frontend Setup 486 | Update the HomePage Component by modifying `/frontend/HomePage.jsx` to fetch and display data from the endpoint you created at `/api/diagnostic`. 487 | 488 | ```javascript 489 | import { useState, useEffect } from 'react'; // import the hooks you are going to use 490 | import axios from 'axios'; 491 | 492 | // Define the HomePage component 493 | function HomePage() { 494 | // useState hook to initialize the diagnosticData state variable to store the fetched data 495 | const [diagnosticData, setDiagnosticData] = useState([]); 496 | 497 | // Define a function to fetch diagnostic data from the API 498 | const fetchDiagnosticData = async () => { 499 | try { 500 | // Construct the URL for the API call 501 | const URL = import.meta.env.VITE_API_URL + 'diagnostic'; 502 | // Use Axios to make the GET request 503 | const response = await axios.get(URL); 504 | // Update state with the response data 505 | setDiagnosticData(response.data); 506 | } catch (error) { 507 | // Handle any errors that occur during the fetch operation 508 | console.error('Error fetching diagnostic data:', error); 509 | alert('Error fetching diagnostic data from the server.'); 510 | } 511 | }; 512 | 513 | // useEffect hook to trigger the fetchDiagnosticData function when the component mounts 514 | useEffect(() => { 515 | fetchDiagnosticData(); 516 | }, []); 517 | 518 | // Determine content based on diagnosticData length from the fetch action 519 | let content; 520 | if (diagnosticData === null) { 521 | content =

Loading diagnostic data...

; // Show while data is null 522 | } else if (diagnosticData.length === 0) { 523 | content =

No diagnostic data found.

; // Show if data is an empty array 524 | } else { 525 | content =
{JSON.stringify(diagnosticData, null, 2)}
; 526 | } 527 | 528 | // display the content and anything else 529 | return ( 530 | <> 531 |

Diagnostic Data

532 | {content} 533 | 534 |

Feel free to add any information you like about your project

535 | 536 | ); 537 | } 538 | export default HomePage; 539 | ``` 540 | 541 | ### Testing the DB Integration 542 | Now that you have set up the express backend route and frontend Homepage fetch, you should now be able to see this when you `npm run start` the dev servers for both backend and frontend. 543 | 544 | 1. Start your backend server and ensure it's running without errors. 545 | 2. Start your React frontend and navigate to the homepage. 546 | 3. You should see the diagnostic data displayed on the page, confirming that the backend and frontend are correctly integrated. 547 | 4. In the browser dev tools network tab you should see the fetch to `/api/diagnostic` with its request and response. 548 | 5. You should see any console.log() statements you added in the backend code in the terminal on flip where the server.js is running. 549 | 550 | ### Building and Submitting the Assignment 551 | By following these steps, you've successfully added a diagnostic API endpoint to your backend and displayed its response in your React frontend. This setup is a foundational step towards building full-stack web applications that require backend and frontend integration. Once you are satisfied that the frontend application is communicating with the backend server.js in the `npm run start` defined dev servers, you will need to take steps to build and serve a working URL that other people on the OSU VPN may observe the functionality of the diagnostic fetch. Any URLs you created with `npm run start` do not persist once you log out of the ssh session on the flip servers. **The link you submit to canvas will need to stay live and functional for TAs and Instructors to complete their grading.** You may choose to modify the configurations of this repo to fit your specific needs for using various OSU servers (flip1, flip2, classwork, etc...). Please refer to the remaining sections below to learn how to build and deploy your react application. The section [Build and Deploy](#build-and-deploy) should help you get started on that. 552 | 553 | 554 | 555 | 556 | ## Understanding Terminal Commands and NPM Scripts 557 | The `package.json` files in both the `/frontend` and `/backend` directories of our project serve as a manifest for project settings, dependencies, and, importantly, scripts that automate tasks. These scripts are custom commands defined under the `"scripts"` property and can be executed using npm or npx, simplifying the development and deployment processes. In this section we will iteratively learn about the various ways you can start up a project server, and how these can be traslated into efficient npm scripts. 558 | 559 | ### Command - `node` 560 | 561 | To set a foundation for why npm scripts make development more efficient, let's first look at how you would start up the `backend/server.js` the traditional way using the command `node server.js`: 562 | 563 | ```sh 564 | flip3 ~/react-starter-app/App/backend 1025$ node server.js 565 | Server running: http://flip3.engr.oregonstate.edu:65432... 566 | ▌ 567 | ``` 568 | You will notice that this command takes over your terminal until you send the `control + C` | `^C` | `SIGINT` command. Another disadvantage of using `node server.js` is that when you make changes to the code, you must stop and restart the process before you can see those changes. 569 | 570 | Now you might be saying to yourself, "Can't I just use `nodemeon` to solve this?"... YES! 571 | 572 | ### Command - `nodemon` 573 | 574 | Let's now look at how you would use `nodemon` in the traditional method. In your backend terminal, you would use the command `npx nodemon server.js` like this: 575 | ```sh 576 | flip3 ~/react-starter-app/App/backend 1027$ npx nodemon server.js 577 | [nodemon] 3.0.2 578 | [nodemon] to restart at any time, enter `rs` 579 | [nodemon] watching path(s): *.* 580 | [nodemon] watching extensions: js,mjs,cjs,json 581 | [nodemon] starting `node server.js` 582 | Server running: http://flip3.engr.oregonstate.edu:65432... 583 | 584 | # I made some changes and saved my code... 585 | [nodemon] restarting due to changes... 586 | [nodemon] starting `node server.js` 587 | Server running: http://flip3.engr.oregonstate.edu:65432... 588 | 589 | # I made some changes and saved my code... 590 | [nodemon] restarting due to changes... 591 | [nodemon] starting `node server.js` 592 | Server running: http://flip3.engr.oregonstate.edu:65432... 593 | 594 | # I made some changes and saved my code... 595 | [nodemon] restarting due to changes... 596 | [nodemon] starting `node server.js` 597 | Server running: http://flip3.engr.oregonstate.edu:65432... 598 | ▌ 599 | ``` 600 | 601 | This now allows us to utilize `nodemon's` hot module reloding (HMR) which automatically restarts the server when code changes are saved. `npx` is a command-line utility bundled with `npm` that executes local node packages. It allows you to run any command available in a local `node_modules` without globally installing the packages or adding them to your `bash_profile` or `bashrc` files. `nodemon` is great, but we still have a problem in that our terminal has been taken over until `control + C` has stopped the process. Another problem is that once we exit out of the terminal or the ssh session on flip, the server will stop running and we will no longer be able to view our active URL. This is where the package `forever` will come in handy. 602 | 603 | ### Installing - `forever` 604 | 605 | The package `forever` allows us to run multiple processes forever which will run outside of a terminal or ssh instance. This package is currently already listed in both `frontend/package.json` and `backend/package.json` dependencies, so when you ran `npm install` earlier, it should have installed that in both `/frontend` and `/backend` for you to be able to access. If for some reason this didn't happen, you can install it directly with `npm install forever --save`. 606 | 607 | > You must run any forever commands from the root of your project (where server.js is located). If you don't it will fail. For this project, that is `/backend` or `/frontend` 608 | 609 | For reasons beyond your control, running `forever` is a bit more complex on the school's FLIP server. Here is how to make it easy, run the following command from the root of your project (`/backend` or `/frontend`): 610 | 611 | ```bash 612 | alias forever='./node_modules/forever/bin/forever' 613 | ``` 614 | 615 | Now, whenever you run `forever` from the *root* of any project that has the forever dependency installed, it will work, without fail. If you want to make this more permanent (not absolutely permanent), you can add this as a line towards the end of the `~/.bashrc` file in your home directory (notice the ~ squiggly) on the OSU FLIP server. 616 | 617 | 618 | Now that we have `forever` installed, let's look at the commands that you might use. 619 | 620 | ### Command - `forever start server.js` 621 | 622 | Running the command `forever start server.js`, is similar to running the command `node server.js` that we learned about prior to this. A difference is that it does not run in the foreground of our terminal, but instead runs in the background. Another difference is that `forever` will automatically restart your application if there are any issues. With this command you can safely exit out of your flip ssh session and submit urls to canvas for other to view them. You can see below how our terminal prompt returns to the screen for us to be able to type further commands of our choice. Runing `forever start server.js` can be done for any nodejs application that you want to start up. 623 | 624 | ```sh 625 | flip3 ~/react-starter-app/App/backend 1032$ forever start server.js 626 | # I have found that these warnings can be safely ignored...so far... 627 | # The warnings will show up every time you run a forever command, they have not caused breaking issues for me yet... 628 | warn: --minUptime not set. Defaulting to: 1000ms 629 | warn: --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms 630 | info: Forever processing file: server.js 631 | (node:4115889) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 632 | (Use `node --trace-warnings ...` to show where the warning was created) 633 | (node:4115889) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 634 | 635 | flip3 ~/react-starter-app/App/backend 1033$ ▌ # we can continue to do things in the terminal prompt! 636 | ``` 637 | 638 | ### Command - `forever list` 639 | 640 | To see a list of your current processes in `forever`, you can use the command `forever list` like this: 641 | ```sh 642 | flip3 ~/react-starter-app/App/backend 1002$ forever list 643 | (node:4144017) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 644 | (Use `node --trace-warnings ...` to show where the warning was created) 645 | (node:4144017) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 646 | 647 | info: Forever processes running 648 | data: uid command script forever pid id logfile uptime 649 | data: [0] z8Fa /nfs/stak/users/maesz/.nvm/versions/node/v16.13.0/bin/node server.js 4143975 4144005 /nfs/stak/users/maesz/.forever/z8Fa.log 0:0:0:2.781 650 | flip3 ~/react-starter-app/App/backend 1003$ ▌ 651 | ``` 652 | Depending on what you have running, this might show multiple processes. There are many values that you can look at here including the location of your logs at `logfile`, but also take note of the index value for this process which is `[0]`, you will need this in the next section... 653 | 654 | ### Command - `forever stop ` 655 | If you need to stop a process, like the one running above at index `[0]`, you can use the command `forever stop ` (ie - `forever stop 0`) to accomplish this: 656 | 657 | ```sh 658 | flip3 ~/react-starter-app/App/backend 1012$ forever stop 0 659 | (node:4147301) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 660 | (Use `node --trace-warnings ...` to show where the warning was created) 661 | (node:4147301) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 662 | 663 | info: Forever stopped process: 664 | uid command script forever pid id logfile uptime 665 | [0] 30Nc /nfs/stak/users/maesz/.nvm/versions/node/v16.13.0/bin/node server.js 4147194 4147226 /nfs/stak/users/maesz/.forever/30Nc.log 0:0:0:5.311 666 | flip3 ~/react-starter-app/App/backend 1013$ ▌ 667 | ``` 668 | This command can be used to stop any of the indexes (0, 1, 2, 3 , etc.) 669 | 670 | ### Command - `forever restart ` 671 | 672 | In the same way that you can stop a process by its index, you can also restart a process by its index. This is useful if you made changes or something broke and you needed to restart. 673 | 674 | ```sh 675 | flip3 ~/react-starter-app/App/backend 1027$ forever restart 0 676 | (node:4158785) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 677 | (Use `node --trace-warnings ...` to show where the warning was created) 678 | (node:4158785) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 679 | 680 | info: Forever restarted process(es): 681 | data: uid command script forever pid id logfile uptime 682 | data: [0] QH6j /nfs/stak/users/maesz/.nvm/versions/node/v16.13.0/bin/node server.js 4158690 4158716 /nfs/stak/users/maesz/.forever/QH6j.log 0:0:0:5.315 683 | flip3 ~/react-starter-app/App/backend 1028$ ▌ 684 | ``` 685 | 686 | This command can be used to restart any of the indexes (0, 1, 2, 3 , etc.) 687 | 688 | ### Command - `forever stopall` 689 | This command will stop every single forever process that is running, **use with caution!** You will notice below that I happened to have four processes running and it stopped all of them. Again, **use with caution!!** 690 | ```sh 691 | flip3 ~/react-starter-app/App/backend 1024$ forever stopall 692 | (node:4155732) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 693 | (Use `node --trace-warnings ...` to show where the warning was created) 694 | (node:4155732) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 695 | 696 | info: No forever processes running 697 | info: Forever stopped processes: 698 | data: uid command script forever pid id logfile uptime 699 | data: [0] n34E /nfs/stak/users/maesz/.nvm/versions/node/v16.13.0/bin/node reactServer.cjs 4154645 4154671 /nfs/stak/users/maesz/.forever/n34E.log 0:0:1:12.748000000000005 700 | data: [1] KgCi /nfs/stak/users/maesz/.nvm/versions/node/v16.13.0/bin/node server.js 4154727 4154768 /nfs/stak/users/maesz/.forever/KgCi.log 0:0:1:6.221000000000004 701 | data: [2] 3z2l /nfs/stak/users/maesz/.nvm/versions/node/v16.13.0/bin/node controllers/peopleController.js 4155077 4155102 /nfs/stak/users/maesz/.forever/3z2l.log STOPPED 702 | data: [3] eoEG /nfs/stak/users/maesz/.nvm/versions/node/v16.13.0/bin/node database/config.js 4155426 4155517 /nfs/stak/users/maesz/.forever/eoEG.log STOPPED 703 | flip3 ~/react-starter-app/App/backend 1025$ ▌ 704 | ``` 705 | 706 | As you can see, `forever` is a very powerful tool that can help us while we develop the react application. Now let's take a look at how npm scripts will help us to simplify all these commands. 707 | 708 | ### NPM Scripts Inside the `/backend/package.json` 709 | 710 | Inside our various `package.json` files we can name and define any script that we want the `npm` command to execute. Here is part of the `backend/package.json` file where I have created two custom scripts called `"start"` and `"serve"`: 711 | ```json 712 | { 713 | "name": "cs340-react-starter-app-backend", 714 | "version": "1.0.0", 715 | "description": "This is the backend express API that connects the frontend to the mariadb database", 716 | "main": "server.js", 717 | 718 | "scripts": { 719 | "start": "nodemon server.js", 720 | "serve": "npx forever start server.js" 721 | }, 722 | ... 723 | ``` 724 | ### Command - npm run start 725 | If we run the command `npm run start`, node package manager will look inside the `"scripts"` section of the `package.json` and find the `"start"` command `nodemon server.js`. Notice how we are utilizing `nodemon` to take advantage of its development features like HMR. `npm run start` will be the command that you run to start the backend server while you are actively working on code. This command will NOT permanently serve the api. So if you ran `npm run start` (ie `nodemon`) and exited out of the flip ssh session, your server would no longer be running. 726 | 727 | ```sh 728 | flip3 ~/react-starter-app/App/backend 1004$ npm run start 729 | 730 | > cs340-react-starter-app-backend@1.0.0 start 731 | > nodemon server.js 732 | 733 | [nodemon] 3.0.2 734 | [nodemon] to restart at any time, enter `rs` 735 | [nodemon] watching path(s): *.* 736 | [nodemon] watching extensions: js,mjs,cjs,json 737 | [nodemon] starting `node server.js` 738 | Server running: http://flip3.engr.oregonstate.edu:65432... 739 | ▌ 740 | ``` 741 | 742 | ### Command - `npm run serve` 743 | 744 | When you need a url to stay active for days (in the case of submitting a project step), you will need to utilize the `"serve"` script which is basically just the command `npx forever start server.js`. As we learned in the prior section, forever will allow our program to run continuously in the background and restart it if necessary. To utilize this npm script, you will run the command `npm run serve` like this: 745 | 746 | ```sh 747 | flip3 ~/react-starter-app/App/backend 1011$ npm run serve 748 | 749 | > cs340-react-starter-app-backend@1.0.0 serve 750 | > npx forever start server.js 751 | 752 | warn: --minUptime not set. Defaulting to: 1000ms 753 | warn: --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms 754 | info: Forever processing file: server.js 755 | (node:764027) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 756 | (Use `node --trace-warnings ...` to show where the warning was created) 757 | (node:764027) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 758 | flip3 ~/ula_cs340/winter24/react-starter-app/App/backend 1012$ 759 | ▌ 760 | ``` 761 | > The `npx` could potentially cause issues for some of you. If it does not work you could try editing the `"serve"` script in the `backend/package.json` to look like this: `"serve": "forever start server.js"` which just removes the `npx` command. 762 | 763 | Now you should have a solid understanding of all the commands and scripts that can be used to run the `/backend` server. We will now take a closer look at the scripts specific to the `/frontend` vite react project. 764 | 765 | 766 | ### NPM Scripts Inside the `/frontend/package.json` 767 | 768 | We already covered the `"start"` script in a the prior section [Command - npm run start](#Command--npm-run-start). In our `/frontend/package.json` you will notice a lot of other scripts that will be very useful for development. Again, you are allowed to modify these scripts to your needs, but I would recommend leaving the `"start"` and `"build"` commands as is because they are utilizing built in vite tooling. 769 | ```json 770 | { 771 | "name": "cs340-react-starter-app-frontend", 772 | "private": true, 773 | "version": "0.0.0", 774 | "type": "module", 775 | "scripts": { 776 | "start": "vite", 777 | "build": "vite build", 778 | "serve": "npx forever start reactServer.cjs", 779 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 780 | "preview": "vite preview" 781 | }, 782 | ``` 783 | 784 | #### `"start": "vite"` 785 | - **What It Does:** This script starts a local development server for your Vite React application. 786 | - **How It Works:** By running `npm run start`, Vite serves your application with hot module replacement (HMR), which means your changes will be updated live without needing to refresh your browser. This essentially runs the terminal command `vite`. 787 | - **Use Case:** Use this command while actively developing your application to see your changes in real-time. 788 | - This was covered in the prior [Option 1 - Local Only section](#option-1---local-only) 789 | 790 | #### `"build": "vite build"` 791 | - **What It Does:** Builds your application for production. 792 | - **How It Works:** Running `npm run build` compiles your React application into static files optimized for production, typically into a `/dist` directory. 793 | - **Use Case:** Run this script when you are ready to deploy your application, creating a version that's optimized for speed and efficiency. 794 | - This is explained more specifically in the next [Build and Deploy Section](#build-and-deploy) 795 | 796 | #### `"serve": "npx forever start reactServer.cjs"` 797 | - **What It Does:** Uses `forever` to serve your production build continuously via a custom express application called `reactServer.cjs`. 798 | - Note: commonJS is required here because of our package.json definition of `"type": "module"`. It is also possible to use ES6, but that is not covered in this guide. 799 | - **How It Works:** After building your application with `npm run build`, run this script `npm run serve` to start the api `reactServer.cjs` that serves your built application located in the `/dist` folder. The forever tool ensures that this server runs continuously, even if the script crashes or the server is restarted. 800 | - **Use Case:** Ideal for when you need your built application to be accessible over the internet for extended periods (For example, when submitting a project step). 801 | - This is explained more specifically in the next [Build and Deploy Section](#build-and-deploy) 802 | 803 | #### `"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0"` (NOT REQUIRED TO USE) 804 | - **What It Does:** Lints your JavaScript and JSX files to ensure code quality and consistency. 805 | - **How It Works:** This script runs ESLint on all .js and .jsx files in your project, enforcing your coding standards. Run it with `npm run lint`. The --report-unused-disable-directives flag reports ESLint disable comments that don't actually suppress any errors, and --max-warnings 0 treats warnings as errors, failing the script if any are found. 806 | - **Use Case:** Run this command to check for and fix linting errors in your codebase, ideally before committing your code. 807 | - This is automatically provided by vite and is not required, but you might find useful. This guide will not discuss this script further, use at your own risk. 808 | 809 | #### `"preview": "vite preview"` (NOT REQUIRED TO USE) 810 | - **What It Does:** Serves your production build locally for testing. 811 | - **How It Works:** After building your project with `npm run build`, this script serves the production version of your application from a local server, allowing you to test the built site before deploying it. Run this with `npm run preview`. 812 | - **Use Case:** Use this to preview the final version of your site in a production-like environment, verifying that everything works as expected before an actual deployment. 813 | - This is automatically provided by vite and is not required, but you might find useful. This guide will not discuss this script further, use at your own risk. 814 | 815 | 816 | Remember, you can always edit these scripts to fit your project needs. These are not set in stone, and they are more like preferences that you get to decide on. For example, you might find that you are not using `forever` but instead using `pm2` (not covered in this guide). This would require you to change the `"serve"` script to reflect your use of the `pm2` package. In theory you can create and name any script you want. For example, if you wanted to use `nodemon` to test if `reactServer.cjs` is working prior to serving it with `forever`, you would create a new script inside the package.json file like this: `"nodemon-server": "nodemon reactServer.cjs"` and then run it in the terminal with the command `npm run nodemon-server`. 817 | > Implementing the nodemon-server script will be left as an exercise for the student. It may require additional configuration. 818 | 819 | Now that we have a high level overview of the `/frontend` npm scripts, let's take a closer look at how the `"build"` and `"serve"` script will work for this react project. 820 | 821 | ## Build and Deploy 822 | 823 | There are hundreds of ways to serve your applications which include but are not limited to the flip servers, netlify, vercel, heroku, google cloud, or even hardware solutions like raspberry pis. This guide will show you how to serve specifically with vite, express, forever, and the flip server. You essentially build your react application with `vite build` then statically serve the build folder (`/dist`) using the express server `reactServer.cjs`. This express server will run in parallel to your `/backend` express mariadb server that we configured in the prior section. At the end of the day, you are running two servers at two different ports. 824 | 825 | This video goes through a very simple react/express application that covers the philosophy of this build process for the frontend server. 826 | [Serve a React app from an Express server | React frontend and Express API setup in 1 project!](https://youtu.be/4pUBO31nkpk?si=3oeBA1u3tScOvNA0) 827 | 828 | If you have not done so already, open up your `/frontend/.env` file and set a port number for the `REACT_SERVER_PORT` variable and save the file. If you recall from the [Frontend Setup (Vite) section](#frontend-setup-vite), our .env file sould look like this (change the ports to ones you are going to use) 829 | ```python 830 | VITE_API_URL='http://flip3.engr.oregonstate.edu:8500/api/' # Change this url to match your backend express api url and port. 831 | VITE_PORT=8501 # Set a port number between:[1024 < PORT < 65535], this should NOT be the same as the API port. 832 | REACT_SERVER_PORT=6061 # This is the port used in the /frontend/reactServer.js to host your '/dist' build folder. [1024 < PORT < 65535] 833 | ``` 834 | 835 | Looking inside of the file `reactServer.cjs`, we will find a very small express server that has one function. This will server our static build files located inside the `/dist` folder. 836 | 837 | #### reactServer.cjs: 838 | ```js 839 | // reactServer.cjs 840 | // Uses common javascript to serve the react build folder (/dist) 841 | 842 | const express = require('express'); 843 | const path = require('path'); 844 | const app = express(); 845 | require("dotenv").config(); 846 | 847 | // Use the custom 'REACT_SERVER_PORT' port from .env, with a fallback to 3001 848 | const PORT = process.env.REACT_SERVER_PORT || 3001; 849 | 850 | // Serve the static files from the React app located in the build folder '/dist' 851 | // React router will take over frontend routing 852 | app.use(express.static(path.join(__dirname, 'dist'))); 853 | 854 | // Handles any requests that don't match the ones above to return the React app 855 | // A request to '/nonExist' will redirect to the index.html where react router takes over at '/' 856 | app.get('*', (req, res) => { 857 | res.sendFile(path.resolve(__dirname, 'dist', 'index.html')); 858 | }); 859 | 860 | app.listen(PORT, () => { 861 | // Change this text to whatever FLIP server you're on 862 | console.log(`Server running: http://flip3.engr.oregonstate.edu:${PORT}...`); 863 | }); 864 | ``` 865 | 866 | Please take notice of two things here in the `reactServer.cjs`: 867 | 1. We create a `PORT` variable for the server to run on that peers into our .env file and pulls out the dotenv variable `REACT_SERVER_PORT` that we just defined. 868 | 2. We use `app.use(...)` and `app.get('*', ...)` to point every url endpoint to our static files located at `/dist` and that folder's `index.html`. 869 | 870 | We will see how this works soon, but first we must build our `/dist` folder. Use the command `npm run build` to do this. 871 | 872 | ```sh 873 | # Build the '/dist' folder... 874 | flip3 ~/react-starter-app/App/frontend 1022$ npm run build 875 | 876 | > cs340-react-starter-app@0.0.0 build 877 | > vite build 878 | 879 | vite v5.1.4 building for production... 880 | ✓ 93 modules transformed. 881 | dist/index.html 0.45 kB │ gzip: 0.30 kB 882 | dist/assets/index-vBDqSkg8.css 0.11 kB │ gzip: 0.11 kB 883 | dist/assets/index-bVtY9NLx.js 201.19 kB │ gzip: 67.30 kB 884 | ✓ built in 6.16s 885 | flip3 ~/react-starter-app/App/frontend 1023$ ▌ 886 | ``` 887 | > You should now see a `/dist` folder was created in the `/frontend` directory! 888 | 889 | Every time you run the command `npm run build` it will replace your old `/dist` with a new version containing all of your newly saved react code and assets. Be careful with this command becasue you will lose the old version of `/dist` that you may have running on your older server. 890 | 891 | #### Serving the Vite dist: 892 | 893 | Now that you have built your `/dist` folder, this static build can be served hundreds of different ways. Since we are already running the express backend on a different flip port, we will use our frontend express server `reactServer.cjs` to serve the `/dist` build folder at a port of our choosing. There is a serve command built into the frontend package.json that will automatically use forever to start up `reactServer.cjs`. 894 | ```sh 895 | # Serve the frontend dist build 896 | flip1 ~/react-starter-app/App/frontend 583$ npm run serve 897 | 898 | > cs340-react-starter-app-frontend@0.0.0 serve 899 | > npx forever start reactServer.cjs 900 | 901 | warn: --minUptime not set. Defaulting to: 1000ms 902 | warn: --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms 903 | info: Forever processing file: reactServer.cjs 904 | (node:1702055) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 905 | (Use `node --trace-warnings ...` to show where the warning was created) 906 | (node:1702055) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 907 | flip1 ~/react-starter-app/App/frontend 584$ ▌ 908 | ``` 909 | 910 | > Assuming that you have the backend server running, if you run the command `forever list` you should see both processes for frontend and backend. 911 | 912 | > Your Website is now visible at the flip server and port number that you set up in `.env`. 913 | ```sh 914 | flip1 ~/react-starter-app/App/frontend 584$ forever list 915 | (node:1702689) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 916 | (Use `node --trace-warnings ...` to show where the warning was created) 917 | (node:1702689) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency 918 | info: Forever processes running 919 | data: uid command script forever pid id logfile uptime 920 | data: [0] xdT7 /nfs/stak/users/maesz/.nvm/versions/node/v16.13.0/bin/node server.js 1701949 1701965 /nfs/stak/users/maesz/.forever/xdT7.log 0:0:10:59.63900000000001 921 | data: [1] 9f1d /nfs/stak/users/maesz/.nvm/versions/node/v16.13.0/bin/node reactServer.cjs 1702085 1702100 /nfs/stak/users/maesz/.forever/9f1d.log 0:0:10:41.789999999999964 922 | flip1 ~/react-starter-app/App/frontend 585$ ▌ 923 | ``` 924 | 925 | ## Build and API Served With Forever 926 | 927 | Here are some images of what the stock build of this website will look like. 928 | 929 | ### API On Separate Port 930 | ![API](images-readme/api.png) 931 | 932 | ### Homepage 933 | ![Home Screen](images-readme/homescreen.png) 934 | 935 | ### BSG People Page 936 | ![BSG People](images-readme/bsgpeople.png) 937 | 938 | ### Add Person Page 939 | ![Add Person](images-readme/addperson.png) 940 | 941 | ### Update Person Page 942 | ![Update Person](images-readme/updateperson.png) 943 | 944 | 945 | 965 | 966 | --------------------------------------------------------------------------------