Skip to main content

React Express & MYSQL - Full Stack Product Admin Panel

In the earlier tutorials, you learnt to create React app, and API endpoints to do authentication with JWT, protect APIs, get and count rows of MYSQL database, and upload form data with files to the database in ExpressJs.

In this tutorial, we continue to develop the React app to build a full stack product admin panel website that accesses ExpressJs API endpoints. The product admin panel app will have a left sidebar that allows a visitor easily to navigate different parts of the app. We use bootstrap to style the app. So run the following command to add bootstrap into the React app.

my-app>npm install bootstrap

Our form components are from reactstrap and icons from react-icons packages. Let execute the commands below to install reactstrap and react-icons.

my-app>npm install reacstrap react-icons


To setup the left sidebar in the app, we start by creating the components folder in src folder. To group sidebar files, add sidebar folder to the components folder. We have three sidebar files in the sidebar folder. The SideBar displays SubMenu and the SubMenu uses data from the SideBarData.

sidebar/SideBar.js
import React, { useState } from "react";
import { Link } from "react-router-dom";
import {FaBars} from "react-icons/fa";
import {AiOutlineClose} from "react-icons/ai";
import { SideBarData } from "./SideBarData";
import SubMenu from "./SubMenu";
import { IconContext } from "react-icons/lib";
import { FaSignInAlt,FaSignOutAlt } from "react-icons/fa"

const nav_style = {
    background: "#0a58ca",
    height: "60px",
    display: "flex",
    justifyCcontent: "flex-start",
    alignItems: "center"
};

const navitem_style = {
    color:"#ffffff",
    marginLeft: "auto", 
    marginRight: "20px"
};
  

const navicon_style = {
  marginLeft: "2rem",
  fontSize: "1.8rem",
  display: "flex",
  justifyContent: "flex-start",
  alignItems: "center",
  cursor: "pointer",
};
  

const sidebarwrap_style = {
    width: "100%",
};

  
const Sidebar = (props) => {
  const [sidebaropen, setSidebarOpen] = useState(false);
  const showSidebar = () => setSidebarOpen(!sidebaropen);
  const {token} = props;

  const sidebarnav_style = {
    background: "#0a58ca",
    width: "250px",
    height: "100vh",
    display: "flex",
    justifyContent: "center",
    position: "fixed",
    top: 0,
    left: (sidebaropen ? "0" : "-100%"),
    transition: "350ms",
    zIndex: 10,

};

  const links = [
      {
      id: 2,
      path: token===null? "/login":"/logout",
      text: token===null?"Login":"Logout",
      icon: token===null?<FaSignInAlt/> : <FaSignOutAlt/>
    },

  ]; 
  
  return (
    <>
      <IconContext.Provider value={{ color: "#fff" }}>
        <div style={nav_style}>
          <div to="#" style={navicon_style}>
            <FaBars onClick={showSidebar} />
          </div>

          <h4
            style={{ textAlign: "center", 
                     marginLeft: "30px", 
                     color: "white" }}
          >
            Admin

          </h4>
          <div style={navitem_style}>
          <ul className="navbar-nav mr-auto list-group-horizontal">
             {links.map(link => {
              return (
                <li key={link.id} className="nav-item">
                  <Link to={link.path} className="nav-link">{link.icon}{link.text}</Link>
                </li>
              )
            })}
          </ul> 
          </div>
        </div>
        <div className="nav" style={sidebarnav_style}>
          <div style={sidebarwrap_style}>
            <div to="#" style={navicon_style}>
              <AiOutlineClose onClick={showSidebar} />
            </div>
            {SideBarData.map((item, index) => {
              return <SubMenu item={item} key={index} />;
            })}
          </div>
        </div>
      </IconContext.Provider>
    </>
  );
};
  
export default Sidebar;

sidebar/SideBarData.js

import React from "react";
import {FaPhone,FaEnvelopeOpenText,FaProductHunt,FaServicestack} from "react-icons/fa";
import {AiFillHome} from "react-icons/ai";
import {IoIosPaper,IoMdHelpCircle} from "react-icons/io";
import {RiArrowUpSFill,RiArrowDownSFill} from "react-icons/ri";
  
export const SideBarData = [
    {
        title: "Products",
        path: "/products",
        icon: <FaProductHunt />,
    },
    {
        title: "Events",
        path: "/events",
        icon: <FaEnvelopeOpenText />,
      
        iconClosed: <RiArrowDownSFill />,
        iconOpened: <RiArrowUpSFill />,
      
        subNav: [
          {
            title: "Event 1",
            path: "/events/events1",
            icon: <IoIosPaper />,
          },
          {
            title: "Event 2",
            path: "/events/events2",
            icon: <IoIosPaper />,
          },
        ],
      },
    {
    title: "Services",
    path: "/services",
    icon: <FaServicestack />,
    iconClosed: <RiArrowDownSFill />,
    iconOpened: <RiArrowUpSFill />,
  
    subNav: [
      {
        title: "Service 1",
        path: "/services/services1",
        icon: <IoIosPaper />,
        cName: "sub-nav",
      },
      {
        title: "Service 2",
        path: "/services/services2",
        icon: <IoIosPaper />,
        cName: "sub-nav",
      },
      {
        title: "Service 3",
        path: "/services/services3",
        icon: <IoIosPaper />,
      },
    ],
  },
  
  {
    title: "About Us",
    path: "/about-us",
    icon: <AiFillHome />,
  },
  {
    title: "Contact",
    path: "/contact",
    icon: <FaPhone />,
  },
  
  {
    title: "Support",
    path: "/support",
    icon: <IoMdHelpCircle />,
  },
];

sidebar/SubMenu.js
import React, { useState } from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
  
const SidebarLink = styled(Link)`
  display: flex;
  color: #e1e9fc;
  justify-content: space-between;
  align-items: center;
  padding: 20px;
  list-style: none;
  height: 60px;
  text-decoration: none;
  font-size: 18px;
  
  &:hover {
    background: #252831;
    border-left: 4px solid green;
    cursor: pointer;
  }
`;
  
const SidebarLabel = styled.span`
  margin-left: 16px;
`;
  
const DropdownLink = styled(Link)`
  background: #252831;
  height: 60px;
  padding-left: 3rem;
  display: flex;
  align-items: center;
  text-decoration: none;
  color: #f5f5f5;
  font-size: 18px;
  
  &:hover {
    background: green;
    cursor: pointer;
  }
`;
  
const SubMenu = ({ item }) => {
  const [subnav, setSubnav] = useState(false);
  
  const showSubnav = () => setSubnav(!subnav);
  
  return (
    <>
      <SidebarLink to={item.path} 
      onClick={item.subNav && showSubnav}>
        <div>
          {item.icon}
          <SidebarLabel>{item.title}</SidebarLabel>
        </div>
        <div>
          {item.subNav && subnav
            ? item.iconOpened
            : item.subNav
            ? item.iconClosed
            : null}
        </div>
      </SidebarLink>
      {subnav &&
        item.subNav.map((item, index) => {
          return (
            <DropdownLink to={item.path} key={index}>
              {item.icon}
              <SidebarLabel>{item.title}</SidebarLabel>
            </DropdownLink>
          );
        })}
    </>
  );
};
  
export default SubMenu;

In the components folder, create Home component to show welcome screen.
import React from "react"

const Home = (props) => {
   
      return (
        <>
        <div className="container">Welcome!</div>
        </>
     
      )
}
export default Home

Then in App.js file, add SideBar, Home components and  import bootstrap to apply styles to all components of the React app. The App reads a token from local storage and sends the token to SideBar to update to  login states.
src/App.js

import "bootstrap/dist/css/bootstrap.min.css";
import './App.css';
import React, { useState } from "react";
import { BrowserRouter as Router, Routes,Route } from "react-router-dom"
import Sidebar from "./components/sidebar/SideBar";
import Home from './components/Home';


function App() {
// read token from local storage
  const [token,setToken] = useState(localStorage.getItem("token"));
  const handleTokenChange = (tk) =>{
    setToken(tk);
  }
  return (

    <div className="App">
      <Router>
        <Sidebar token={token} />
      <Routes>
        <Route path="/" element ={<Home />}/>
        </Routes>
      </Router>
    </div>

  );
}

export default App;


Save the project. Here is the output after the app reloads:



In the components folder, create Register, Login, and Logout components. The Register component allows a visitor to create a new user account using username, email, and password. We do form validations by allowing the username to start with letters and contain letters and numbers only. The password length must be greater than or equal to 8, and the email must be in the correct format. 
When the registration is successful, the user is redirected to the login screen.

components/register.js

import React, { useState } from "react"
import axios from "axios";
import { useNavigate } from "react-router-dom";

import {
  Button,
  FormGroup,Card,
  CardHeader,CardBody,CardFooter,  
  Input,
  Form,
} from "reactstrap";

const Register = (props) => {

      const [errors,setErrors] = useState({});  
       const [inputs, setInputs] = useState({});
      const history=useNavigate();

      function handleValidation() {
        
        let formIsValid = true;
        let es = {};
        //Name
        if (!inputs.username) {
          formIsValid = false;
          es['username']="can not empty!";
         
        }
    
        if (typeof inputs.username !== "undefined") {
            
          if (!inputs.username.match(/^[a-zA-Z0-9]+$/)) {
            formIsValid = false;
            es['username']="only letters and numbers allowed!";
          }
        }
        // password
        if (!inputs.password) {
            formIsValid = false;
            es['password']="can not empty!";
          }
      
          if (typeof inputs.password !== "undefined") {
            if (inputs.password.length<8) {
              formIsValid = false;
              es['password']="week password!";
            }
          }
    
        //Email
        if (!inputs.email) {
          formIsValid = false;
          es['email']="can not empty!";
        }
    
        if (typeof inputs.email!== "undefined") {
        
          if (!(/\S+@\S+\.\S+/.test(inputs.email))) {
            formIsValid = false;
            es['email']="invalid email!";
          }
        }
        setErrors(es);
        return formIsValid;
      }

      function handleSubmit(e){
        e.preventDefault();
        if(handleValidation()){
            axios({
                method: 'post',
                url: "/api/createuser",
                data: {username: inputs.username,password:inputs.password,email:inputs.email},
                headers: { "Content-type":"application/json",}
            }).then(response=>{

                    try {
                        
                        let dt=JSON.parse(JSON.stringify(response.data));
                        if(dt.message==='success'){
                            history("/login");
                        }
                        else{
                            alert('Failed to create user!');
                        }
                        
                    } catch (e) {
                        alert('Failed to create user!');
                    }
                    
                    
                    
            });
        }
      }
      
      const handleChange = (event) =>{
        const name = event.target.name;
        const value = event.target.value;
        setInputs(values => ({...values, [name]: value}))
      }
      return (
        <div className="container"  style={{width: '18rem'}}>
            <Card
                className="my-2" style={{width: '18rem'}}
            >

            <CardHeader>Register</CardHeader>
            
            <Form className="form">
                <CardBody>
                <FormGroup>
                    <Input
                        type="text"
                        name="username"
                        placeholder="Enter username"
                        value={inputs.username || ""} 
                        onChange={handleChange}
                       
                        />
                    <span style={{color: '#ff2222'}}>{errors.username}</span>
                    </FormGroup>
                   
                    <FormGroup>

                    <Input
                        type="email"
                        name="email"
                        placeholder="Enter email"
                        value={inputs.email || ""} 
                        onChange={handleChange}
    
                        />
                    <span style={{color: '#ff2222'}}>{errors.email}</span>
                    </FormGroup>
                    <FormGroup>

                        <Input
                        type="password"
                        name="password"
                        placeholder="Enter password"
                        value={inputs.password || ""} 
                        onChange={handleChange}
                       
                        />
                    <span style={{color: '#ff2222'}}>{errors.password}</span>
                    </FormGroup>
                </CardBody>   

                <CardFooter>
                    <FormGroup>
        
                    <Button color="primary" onClick={handleSubmit}>Register</Button>
                    </FormGroup>
                </CardFooter>
            </Form>  
            </Card>      
        </div>
      
      )
}
export default Register

Axios is used to access ExpressJs API endpoints. Let install it.
npm install axios

The API endpoint to create a new user in MYSQL database is http://localhost:5000/api/createuser. So we need to proxy from React running port 3000 to ExpressJs server port 5000. By making the proxy from port 3000 to 5000, we do not need to write full API endpoint url. Simply write /api/createuser instead of http://localhost:5000/api/createuser.

To make the proxy work, add proxy to package.json file in the React app.
........ 
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "homepage": ".",
  "proxy": "http://localhost:5000",
.......

The Login component shows a login screen to authenticate the visitor using username and password. The user data is passed to /api/auth API endpoint to be verified. If the verification is successfully, a token will be generated on the server and returned to the Login component. Then the Login component is able to get the token, and save it to local storage. Then, the Login component informs a token change to its parent component (App.js) to update login status in SideBar and redirects the user to products admin panel page. If the login is not successful, the visitor will see an error message at the bottom of the login screen.

components/Login.js
import React, { useState } from "react"
import axios from "axios";
import { useNavigate } from "react-router-dom";
import { Link } from "react-router-dom"
import {
  Button,FormGroup,Card,CardHeader,
  CardBody,CardFooter,  
  Input,Form,Label,
} from "reactstrap";

const Login = (props) => {
      const [username, setUsername]=useState('');
      const [password,setPassword]=useState('');
      const [logerr,setLogError] = useState('');  
      const {tokenchange} = props;
      const history=useNavigate();
      
      function handleTokenChange(tk){
            tokenchange(tk);
      }

      function handleSubmit(e){
        e.preventDefault();
   
        // Make the POST call to login API end point
        axios({
            method: 'post',
            url: "/api/auth",
            data: {username: username,password:password},
            headers: { "Content-type":"application/json",}
        }).then(response=>{
                console.log("token",response.data);
   
                try {
                    let dt=JSON.parse(JSON.stringify(response.data));
                    if(dt.message==='success'){

                        localStorage.setItem("token", dt.token);
                        handleTokenChange(dt.token);
                        history("/products");
                    }
                    else{
                        setLogError(dt.message);
                    }
                    
                } catch (e) {
                    setLogError('Invalid user!');
                }
                
                
                
        });
      }
      const handleUsernameInputChange = (event) => {
        setUsername(event.target.value);
      }
      const handlePasswordInputChange = (event) => {
        setPassword(event.target.value);
      }
      return (
        <div className="container"  style={{width: '25rem'}}>
            <Card
                className="my-2" style={{width: '25rem'}}
            >

            <CardHeader>Login</CardHeader>
            
            <Form className="form">
                <CardBody>
                    <FormGroup>

                    <Input
                        type="text"
                        name="username"
                        placeholder="Enter username"
                        value={username}
                        onChange={(e) =>handleUsernameInputChange(e)}
                        required
                        />
        
                    </FormGroup>
                    <FormGroup>

                        <Input
                        type="password"
                        name="password"
                        placeholder="Enter password"
                        value={password}
                        onChange={(e) =>handlePasswordInputChange(e)}
                        required
                        />
        
                    </FormGroup>
                </CardBody>   

                <CardFooter>
                    <FormGroup>
        
                    <Button color="primary" onClick={handleSubmit}>Login</Button>
                    <Label style={{marginLeft: '5px'}}>Don't have an account?</Label>  <Link to="/register">create user</Link>
                    <Label className="text-danger">{logerr}</Label>
                    
                    </FormGroup>
                </CardFooter>
            </Form>  
            </Card>      
        </div>
      
      )
}
export default Login

The Logout simply removes the stored token from the local storage and redirect the user to the Home page.
components/Logout.js
import React,{useEffect} from "react"
import { useNavigate } from "react-router-dom";

const Logout = (props) => {

      const {tokenchange} = props; 
      const history=useNavigate();
      function handleTokenChange (tk) {
          tokenchange(tk); // inform token change to update logout status in SideBar
      }

      useEffect(() => {
        localStorage.removeItem('token');
        handleTokenChange(null);
        history("/");
         }, []);

      return (
        <><div>Logout work</div></>
      
      )
}
export default Logout

The ProductMain component is the product admin panel that allows an authorized user to manage products. He/she can view products list, sort the products by clicking the column headers, search for products, add new product with images, update, and delete products from the MYSQL database. To access restricted APIs, you need to pass the token via axios request headers to the sever. If the token is not valid, subsequent requests will fail. 

components/ProductMain.js
import React,{ useState, useEffect } from "react";
import axios from "axios";
import SearchBar from "./SearchBar";
import { FaTrash } from "react-icons/fa"
import { FaEdit } from "react-icons/fa"
import { NavLink, Navigate } from "react-router-dom";
import ProgressBar from "./ProgressBar";

import {
    Label,Form,FormGroup,Input,
    Button,Col,Table,
    Modal, ModalHeader, ModalBody, ModalFooter,
  } from "reactstrap";


// global variables
var start=0;
var step=5;
var search='';

function ProductMain(props) {
    //vairables/
    const [products,setProducts]=useState(null);
    const [modal,setModal]=useState(false);
    const [id,setId]=useState(0);
    const [title,setTitle]=useState('');
    const [description,setDescription]=useState('');
    const [price,setPrice]=useState(0.0);
    const [document,setDocument]=useState(null);
    const [refresh,setRefresh]=useState(false);
    const [sorted,setSorted]=useState(false);
    const [isEditMode,setIsEditmode]=useState(false);
    const [numRows,setNumRows]=useState(0);
    const hstyle={textDecoration: 'none'};
    const [files,setFiles]=useState(null);
    const [progressing,setProgressing]= useState(false);
    const token= localStorage.getItem("token");
    
    const pstyle={
      marginRight: '20px',
      textDecoration: 'none',
     };
   
    useEffect(() => {
      axios
     
      .get("/api/products?search="+search+"&start="+start+"&step="+step,{
        headers:{
            
            "authorization": `Bearer ${token}`,
        },

       
      })
      .then((res) => {
        parseResult(res.data);
       
      
      }) 
      .catch((err) => console.log(err));
     }, [refresh]);


     const handleSubmit = (e) => {
      e.preventDefault();
  
      let form_data = new FormData();
      form_data.append('id', id);
      form_data.append('title', title);
      form_data.append('description', description);
      form_data.append('price',price);
      if(document!=null){
        for (let i = 0; i < document.length; i++) {
          
          form_data.append('document', document[i],document[i].name);
          
        }
      }

         for (var key of form_data.entries()) { 
        console.log(key[0] + ', ' + key[1]);
      }

      setProgressing(true);
        // Make the POST call by passing a config object to the instance
      axios({
        method: isEditMode?'put':'post',
        url: "/api/products",
        data: form_data,
        headers: { "Content-type":"application/json",
        "authorization": `Bearer ${token}`,
        }
      }).then(res=>{
        console.log("res=",res);
        let dt=JSON.parse(JSON.stringify(res.data));
        if(dt.message==='success'){
          if(search!=='') searchNow(search);
          else setRefresh(!refresh); 
          setProgressing(false);
          toggle();
        }
        else{
          if(isEditMode)
            alert("Failed to update record");
          else 
            alert("Failed to insert record");  
        }
      });

    };


    const searchNow= (q) =>{
   
            start=0; // reset offset
            search=q;
            axios({
              method: 'get',
              url: "/api/products?search="+search+"&start="+start+"&step="+step,
              headers: { "Content-type":"application/json","authorization": `Bearer ${token}`}
            }).then(response=>{

              parseResult(response.data);
              
            });
        
      };
    const deleteProduct = (id) => {
      axios({

        url: "/api/products/"+id+"/",
        headers: { "Content-type":"application/json","authorization": `Bearer ${token}`}
      }).then(response=>{
        console.log("deleted",response.data);
        setRefresh(!refresh); 
        
      });
      };  
    const editProduct = (item) => {
       
       let imgs= products
        .filter(product => {
          return (
            product.id === item.id
          );
        }).map((product,index) =>{

          return(product.Images);
        });

        setFiles(imgs[0]);
        setIsEditmode(true);
        setId(item.id);
        setTitle(item.title);
        setDescription(item.description);
        setPrice(item.price);
        toggle(!modal);
      };    
    function handleChangeTitle(e){
      setTitle(e.target.value);
    }  
    function handleChangeDescription(e){
      setDescription(e.target.value);
    }  
    function handleChangePrice(e){
      setPrice(e.target.value);
    }  
    
  function toggle() {

    setModal(!modal);
    

  }
  function openAddNewModel() {
    setIsEditmode(false);
    setFiles(null);
    
    toggle();

  }
  const handleImageChange = (e) => {
    setDocument(e.target.files);
  };
  const handlePaging = (mv) =>{
      if(mv==='next') {
        if(start<numRows-step) start=start+step;
      }
      else if(mv==='prev') {
        if(start>=step) start=start-step;
      
      }
 
       axios({
        method: 'get',
        url: "/api/products?search="+search+"&start="+start+"&step="+step,
        headers: { "Content-type":"application/json","authorization": `Bearer ${token}`}
      }).then(response=>{
      
        parseResult(response.data);
        
      });

  }
  const parseResult = (data) =>{
    let dt=JSON.parse(JSON.stringify(data));
    //console.log("dt=",data);
    if(dt.message==='success'){
      setProducts(dt.data);
      setNumRows(dt.numrow);
    }
}
  const handleHeaderClick = (field) =>{
     // sort records
     if(field!=='price') products.sort((a,b) => a[field].localeCompare(b[field]));
     else products.sort((a,b) => b[field]-a[field]);
     // refresh UI
     setSorted(!sorted);
    

      
  }

  const MAX_PERCENTAGE = 100;
  const MIN_PERCENTAGE = 45;
  const [percentage, setPercentage] = React.useState(MAX_PERCENTAGE);
  useEffect(() => {
    const timeout = setTimeout(() => {
      setPercentage(
        percentage === MAX_PERCENTAGE ? MIN_PERCENTAGE : MAX_PERCENTAGE
      );
    }, 2000);

    return () => {
      window.clearTimeout(timeout);
    };
  }, [percentage]);

    if(token===null) return <Navigate to="/login" />

    return (
      <>
    <SearchBar searchNow={searchNow}/>
    
    <div className="container">
     
      <h1>Products</h1> 
      
      {
      <Table striped bordered hover>
      <thead>
      <tr><th><NavLink style={hstyle} to={"#"} onClick={() => handleHeaderClick("title")}>Title</NavLink></th><th ><NavLink style={hstyle} to={"#"} onClick={() => handleHeaderClick("description")}>Description</NavLink></th><th><NavLink style={hstyle} to={"#"} onClick={() => handleHeaderClick("price")}>Price</NavLink></th><th colSpan={2}></th></tr>  
      </thead>
      <tbody>
      {
       products && products.map((item, i) => {
        return (
            <tr key={item.id}>
            <td >{item.title}</td>
            <td >{item.description}</td>
            <td >{item.price}</td>
            <td > <Button color="primary"  onClick={() =>editProduct(item)}>
            <FaEdit style={{ color: "white", fontSize: "12px" }} />

            </Button></td>
            <td > <Button color="primary" onClick={() =>deleteProduct(item.id)}>
            <FaTrash style={{ color: "red", fontSize: "12px" }} />

            </Button></td>
            </tr>
        );	

         })
      }
      </tbody>
    </Table> 
    }
    {

      (start-step>=0 && numRows>step) && <NavLink onClick={() =>handlePaging('prev')} style={pstyle} to={"#"}>{"<"}</NavLink>
      
      }  
    {

     (numRows-start>=step  && numRows>step) && <NavLink abc={numRows} onClick={() =>handlePaging('next')}   style={pstyle} to={"#"}>{">"}</NavLink>  
    }
    <div style={{float: 'right'}}>
      <Button color="primary"  onClick={openAddNewModel}>Add New</Button>
    </div>
    <Modal isOpen={modal} toggle={toggle}>
          <ModalHeader toggle={toggle}>{isEditMode?'Edit Product':'Add Product'}</ModalHeader>
          <ModalBody>
            
        <Form>
        <FormGroup row>
          <Label for="title" sm={2} size="lg">Title</Label>
          <Col sm={10}>
            <Input type="text" value={title} name="title" id="title" placeholder="Title" bsSize="lg" onChange={(e) =>handleChangeTitle(e)} required />
          </Col>
        </FormGroup>
        <FormGroup row>
          <Label for="description" sm={2}>Description</Label>
          <Col sm={10}>
            <Input type="textarea" value={description} name="description" id="description" placeholder="Description"  onChange={(e) =>handleChangeDescription(e)} required />
          </Col>
        </FormGroup>
        <FormGroup row>
          <Label for="price"  sm={2}>Price</Label>
          <Col sm={10}>
            <Input type="number" value={price} name="price" id="price" placeholder="Price"  onChange={(e) =>handleChangePrice(e)} required />
          </Col>
        </FormGroup>
        <FormGroup>
            <p><Label for="file">File</Label>
              <Input 
                multiple
                type="file"
                id="document"
                name="document"
				        accept="image/png, image/jpeg"  onChange={handleImageChange} 
              
              />
              {
                files && files.map((item, i) =>{
                  return(
                   
                    <img style={{marginRight: "5px", width: "50px",height: "50px"}} src={`http://localhost:5000/${item.fileUrl}`} />
                   
                  );
                })
              }
              </p>
              <p>
              {
                  progressing &&
                  <ProgressBar bgcolor={"#6a1b9a"} completed={percentage} />
                  }
              </p>
            </FormGroup>
      </Form>

          </ModalBody>
          <ModalFooter>
            <Button color="primary" onClick={handleSubmit}>Ok</Button>
            <Button color="secondary" onClick={toggle}>Cancel</Button>
          </ModalFooter>
        </Modal>
      
    </div>
    </>
  );
}

export default ProductMain;

components/ProgressBar.js displays a progress bar when the form data is submitted. It lets the user know what is going on and wait until the form is uploaded completely.

const ProgressBar = (props) => {
    const { bgcolor, completed } = props;
 
    const containerStyles = {
      height: 20,
      width: '200px',
      backgroundColor: "#e0e0de",
      borderRadius: 50,
      margin: 50
    }
 
    const fillerStyles = {
      height: '100%',
      width: `${completed}%`,
      backgroundColor: bgcolor,
      transition: 'width 1s ease-in-out',
      borderRadius: 'inherit',
    }
 
    const labelStyles = {
      padding: 5,
      color: 'white',
      fontWeight: 'bold'
    }
 
    return (
      <div style={containerStyles}>
         
        <div style={fillerStyles}>
          <span style={labelStyles}>Uploading...</span>
        </div>
      </div>
    );
  };
 
  export default ProgressBar;

Finally, add the Register, Login, Logout, and ProductMain components to App.js.

import "bootstrap/dist/css/bootstrap.min.css";
import './App.css';
import React, { useState } from "react";
import ProductMain from './components/ProductMain';
import { BrowserRouter as Router, Routes,Route } from "react-router-dom"
import Sidebar from "./components/sidebar/SideBar";
import Home from './components/Home';
import Login from './components/login';
import Register from './components/register';
import Logout from './components/logout';


function App() {
  const [token,setToken] = useState(localStorage.getItem("token"));
  const handleTokenChange = (tk) =>{
    setToken(tk);
  }
  return (

    <div className="App">
      <Router>
        <Sidebar token={token} />
      <Routes>
        <Route path="/" element ={<Home />}/>
        <Route path="/products" element ={<ProductMain />}/>
        <Route path="/login" element ={<Login tokenchange={handleTokenChange} />}/>
        <Route path="/register" element ={<Register />}/>
        <Route path="/logout" element ={<Logout tokenchange={handleTokenChange} />}/>
      </Routes>
      </Router>
    </div>

  );
}

export default App;


Save project. Congratulation! You have developed a full stack website using React Express & MYSQL.





Video Demo

Comments

Popular posts from this blog

Upload form data with files in ExpressJs using Multer & Mysql

In the previous tutorial, you learn to create /products route with  get method of router in Express with Mysql to fetch and filter data using findAndCountAll method. Now we move on creating APIs to allow client apps to upload form data files using POST and PUT method, and delete rows from database using DELETE method. To upload form data with files, we use Multer library. By using Multer, you can easily upload files to local storage or cloud storage like amazon s3.  Execute the following command to install Multer in to ExpressJs app. npm install multer To use Multer, you have to import it into routes/product.routes.js file: ....... const { Op } = require ( "sequelize" ); const multer = require ( 'multer' ); Write the following code to configure Multer to use public folder of the local storage: var storage = multer . diskStorage ({   destination : function ( req , file , callback ) {     callback ( null , './public' );   },   filename : function ( re

React Express & Mysql: Sequelize

Now, you have React project in my-app folder and Express project in prod_project folder. We are going to create back-end APIs that will be accessed by our React app. If you don't have React and Express apps set up, you go to the page Product CRUD Project In the prod_project folder, run the following command to install dependencies that are required to create the APIs on Express server. D:\prod_project> npm install sequelize sequelize-cli mysql2 jsonwebtoken cors body-parser bcrypt sequelize helps us synchronize the models (defined later in our project) with Mysql database. From the models, you can create tables in the database, query, add, update, delete data in the tables. sequelize-cli - Sequelize Command Line Interface helps us create models, migrations, and database. mysql2   is a fast mysql driver to work with mysql database from Express. jsonwebtoken works in user authentication by token for securely transmitting data between our back-end Express and front-end