From Concept to Completion: Building a Full-Stack App with Image Functionality

Build a full-stack web app with MERN tech and Cloudinary

ยท

18 min read

From Concept to Completion: Building a Full-Stack App with Image Functionality

Intro

In this blog, we are going to create a full-stack web application that allows users to sign up, log in, and save images on the database. For this web application, we are going to use ReactJS for frontend, NodeJS, and ExpressJS for the backend server, Cloudinary for uploading images, and MongoDB for saving user details and image links.

So, what are we waiting for? Let's go...

1. Creating the BackEnd

Here we are going to create the backend server. First name your project, I am going to name it as image-app , you can name it of your choice.

In the image-app, create a backend folder and open it in VS Code( or any other editor).

Open the terminal and run the command:-

npm init -y

It will create a package.json file. Now, to create a backend server, we are going to install some packages:-

  • Express JS for creating RESTful APIs.

  • Mongoose for simplifying interactions with the MongoDB database.

  • Zod for data validation.

  • Cors that allows a web page to share resources across different origins.

Before proceeding to the other things, get a connection string that helps to connect the database with the backend, to get a connection string log on to https://www.mongodb.com/, then go to Create a new Project, and go to the overview tab, click on Create as shown in the figure.

After finishing, you will be directed to the home page. Click on the connect button and get the connection string from there.

The connection string would look like this:-

mongodb+srv://<user>:<password>@something.something.mongodb.net/

  1. Connecting the Database to the backend server

    Create a folder named db in the backend folder, and create index.js file.

    In this file, we are going to connect with the MongoDB database using Mongoose and also create schemas for User and Image.

const mongoose = require("mongoose");
mongoose.connect(
  "mongodb+srv://<user>:<password>@something.something.mongodb.net/ "
);

const UserSchema = new mongoose.Schema({
  firstName: {
    type: String,
    required: true,
    trim: true,
    maxLength: 15,
  },
  lastName: {
    type: String,
    require: true,
    trim: true,
    maxLength: 15,
  },
  password: {
    type: String,
    required: true,
    minLength: 8,
  },
  email: {
    type: String,
    unique: true,
    required: true,
  },
});

const ImageSchema = new mongoose.Schema({
  user: {
    ref: "User",
    type: mongoose.Schema.Types.ObjectId,
    required: true,
  },
  imageName: {
    type: String,
    required: true,
  },
  imageLink: {
    type: String,
    required: true,
  },
  cloudinary_id: {
    type: String,
    required: true,
  },
});

const User = mongoose.model("User", UserSchema);
const Image = mongoose.model("Image", ImageSchema);

module.exports = { User, Image };

Now you are going to ask why we have cloudinary_id and imageLink field in the image schema, it is because when we upload images to the Cloudinary, it returns a unique ID, and public link for the image to get access.

We have created schemas for the user and image. It's now time to create routes for them. But before that, it's a good practice to start the server and for that, we have to create a index.js file in the backend folder.

const express = require("express");
const app = express();
const rootRouter = require("./routes/index");
const cors = require("cors");

app.use(cors());
// to parse JSON data sent in the request body
app.use(express.json());
app.use(
  express.urlencoded({
    extended: true,
  })
);

app.use("/api/v1", rootRouter);

app.listen(3000, (req, res) => {
  console.log("Server is running");
});

we have used app.use("/api/v1",rootRouter) in the index.js file. This is where we are going to define our routes.

  1. Creating Routes for the User and Image

In the backend folder, create a folder named routes and create index.js , user.js , and image.js files.

index.js file - This is where routes will be divided for user and image.

const { Router } = require("express");
const router = Router();
const UserRoute = require("./user");
const ImageRoute = require("./image");

router.use("/user", UserRoute);
router.use("/image", ImageRoute);

module.exports = router;

user.js file - This is where we are going to write user routes and logic for creating a new user in the database.

As we know, users will write their email and password, it's a good practice to encrypt their password and save the encrypted password in the database.

For that purpose, we are going to install another npm package called bcryptjs .

Run the command npm i bcryptjs in the terminal.

First, we generate salt using genSalt function which comes with this package, with which we will hash the password using hash function. Now, when the user tries to log in we will compare this hashed password with the input password by using compare function present in the bcryptjs package.

Also, we are going to use jsonwebtoken package for stateless authentication, to learn more about jsonwebtoken you can go to https://jwt.io/.

For creating a token we use sign function and for verifying the token we use verify function. We will see the use of all these things in our user.js file. We will give this token in the request headers.

Earlier I told you that Zod for validating data, we are going to use Zod here. We use Zod for TypeScript-friendly schema validation, ensuring type safety and clean, reusable code with concise error reporting. Its declarative syntax and extensive features simplify data validation and serialization tasks in our applications. You can learn more about Zod on https://zod.dev/

user.js file will look like this:-

const { Router } = require("express");
const router = Router();
const zod = require("zod");
// User Schema from index.js file present in the db folder
const { User } = require("../db/index");
const jwt = require("jsonwebtoken");
const JWT_SECRET = "secretkey";
const bcrypt = require("bcryptjs");

// using zod for the validation checkpoint before signing in
const validateSignUpUser = zod.object({
  firstName: zod.string(),
  lastName: zod.string(),
  password: zod.string(),
  email: zod.string().email(),
});

// signing route for the user
router.post("/signUp", async (req, res) => {
  const firstName = req.body.firstName;
  const lastName = req.body.lastName;
  const password = req.body.password;
  const email = req.body.email;

// safeParsing the input fields using zod
  const isValid = validateSignUpUser.safeParse({
    firstName,
    lastName,
    password,
    email,
  });
// isValid return success in the form of true or false.

  try {
    // if true
    if (isValid.success) {
    // First we will check that is user is already present with that
    // email of not.
      const check = await User.find({ email });
      if (check.length !== 0) {
        res.status(411).json({
          success: false,
          message: "Email already taken / Incorrect Inputs",
        });
      } else {
        // generating salt
        const salt = await bcrypt.genSalt(10);
        // hashing the password
        const secPass = await bcrypt.hash(password, salt);
        // creating the user and saving the information in the database
        const user = await User.create({
          firstName,
          lastName,
          email,
          password: secPass,
        });

        const data = {
          user: {
            id: user.id,
          },
        };
        // creating a token using sign function
        const token = jwt.sign(data, JWT_SECRET);

        res.status(200).json({
          success: true,
          message: "User Created Successfully",
          token: token,
        });
      }
    }
  } catch (error) {
    res.status(411).json({
      success: false,
      message: "wrong Credentials",
    });
  }
});

// validation checkpoint logging in
const validateSignInUser = zod.object({
  email: zod.string().email(),
  password: zod.string(),
});

// log in route for the user
router.post("/logIn", async (req, res) => {
  const email = req.body.email;
  const password = req.body.password;

  const isValid = validateSignInUser.safeParse({
    email,
    password,
  });

  try {
    if (isValid.success) {
      const user = await User.findOne({ email });
      if (!user) {
        return res.status(400).json({
          success: false,
          message: "Please try to login with correct credentials",
        });
      }
      // comparing the hashed password and the input password
      const passwordCompare = await bcrypt.compare(password, user.password);
      if (!passwordCompare) {
        return res.status(400).json({
          success: false,
          message: "please try to login with correct credentials",
        });
      }
      const data = {
        user: {
          id: user.id,
        },
      };
      const token = jwt.sign(data, JWT_SECRET);
      res.status(200).json({
        token: token,
        success: true,
        message: "User logged in successfully",
      });
    }
  } catch (error) {
    res.status(411).json({
      success: false,
      message: "Error!!!",
    });
  }
});

module.exports = router;

And the routes for the user are created successfully. We have only created the signup and login routes. If you want to create routes for updating the details, getting user info, etc, you can create.

Don't you think, we should allow only authenticated users to upload images? Yes, you are right!!! We should. So, before the images route, we should make sure that only authenticated users are allowed. For that, we have to create a middleware.

In the backend folder, create another folder named middleware and create a file called authMiddleware.js . In this file, we will write a logic for giving access to only authenticated users.

Here we are going to use the token that we created while signing up (or logging in). The token would be present in the request headers.

authMiddleware.js file looks like this:-

const jwt = require("jsonwebtoken");
const { User } = require("../db");
const { JWT_SECRET } = require("../config");

// middleware for checking user is authenticated or not
const authMiddleware = async (req, res, next) => {
  // getting the authorization header
  const header = req.headers.authorization;
  try {
    if (header.startsWith("Bearer ")) {
      const token = header.split("Bearer ")[1];
      // verifying the token
      const verify = jwt.verify(token, JWT_SECRET);
      if (verify) {
        const id = verify.user.id;
        // checking whether the user is present or not in the database
        const check = await User.findById(id);
        if (check) {
          // saving the user id the requests, we will understand in image
          // routes why do we need it.
          req.id = id;
          next();
        } else {
          res.status(404).json({});
        }
      }
    }
  } catch (error) {
    res.status(403).json({});
  }
};

module.exports = { authMiddleware };

Our backend server is almost ready, the only thing remaining is creating an image route.

  1. Using Cloudinary and Multer

As we discussed earlier, we are going to save the image on Cloudinary, and the public link on the MongoDB database.

So, we have to install the cloudinary package. Also, we are going to use another library called multer for checking file uploading is an image. There are many use cases of multer but we are using it only for checking whether the uploaded file is an image.

We have to log on to cloudinary.com , and then go to the dashboard where you will see the API key, cloud name, and other necessary things for connecting it using node.js

Make a config.js file in the backend folder, and save these keys into that file.

const CLOUD_NAME = "cloudname";
const API_KEY = "apikey";
const API_SECRET = "secretapi";

module.exports = { CLOUD_NAME, API_KEY, API_SECRET };

Now we have to connect the cloudinary from our backend and have to write login for multer .

Create a utils folder in the backend , and create two files named cloudinary.js and multer.js .

cloudinary.js looks like this:

const { CLOUD_NAME, API_KEY, API_SECRET } = require("../config");

const cloudinary = require("cloudinary").v2;
cloudinary.config({
  cloud_name: CLOUD_NAME,
  api_key: API_KEY,
  api_secret: API_SECRET,
});
module.exports = cloudinary;

multer.js looks like this:

const multer = require("multer");
const path = require("path");

// for checking uploaded file is image
module.exports = multer({
  storage: multer.diskStorage({}),
  fileFilter: (req, file, cb) => {
    let ext = path.extname(file.originalname);
    if (ext !== ".jpg" && ext !== ".jpeg" && ext !== ".png") {
      cb(new Error("Unsupported file type!"), false);
      return;
    }
    cb(null, true);
  },
});

Now, let's create our most awaited image route.

In the image.js file, we will have two routes, one for uploading and one for getting the image details(i.e, public link, image id, etc)

image.js looks like this:

const { Router } = require("express");
const router = Router();
const cloudinary = require("../utils/cloudinary");
const upload = require("../utils/multer");
const { Image } = require("../db/index");
const { authMiddleware } = require("../middleware/authMiddleware");

// getting all the images uploaded by an user
router.get("/getImages", authMiddleware, async (req, res) => {
  try {
    // we had saved user id in the request field with the help of the 
    // authMiddleware, now we are using it to find the images uploaded
    // by that user
    const images = await Image.find({ user: req.id });
    res.status(200).json({
      success: true,
      message: "Images received",
      images,
    });
  } catch (error) {
    res.status(404).json({
      success: false,
      message: "Can't find images",
    });
  }
});

// uploading an image
router.post(
  "/upload",
  authMiddleware,
  upload.single("image"),
  async (req, res) => {
    try {
      // uploading the image on the cloudinary
      const result = await cloudinary.uploader.upload(req.file.path);
      // creating the image doc
      const image = new Image({
        user: req.id,
        imageName: req.body.name,
        imageLink: result.secure_url,
        cloudinary_id: result.public_id,
      });
      // saving it to the database
      await image.save();
      res.status(200).json({
        success: true,
        image: image,
      });
    } catch (error) {
      res.status(404).json({
        success: false,
        message: "bad request",
      });
    }
  }
);

module.exports = router;

Yayyy!!! Our backend has been created successfully. You can test it on Postman or ThunderClient (Vs Code extension).

2. Creating the FrontEnd

For now, we are not going to use transitions, animations, or any other fancy stuff. We will be keeping it simple and sorted. You can add styles if you want to.

I'll be using Bootstrap to save the time from creating the CSS.

In the image-app folder, create a vite react app named frontend .

If you are new to vite, then open a terminal in the image-app and run the command npm create vite@latest then you will be asked the Project name, write frontend and hit enter, now you have to select a framework select React

then you will be asked to select a variant, select JavaScript

You are ready to go...

Open it in the vs code.

We know React is all about making components and using them. So, we are going to make components.

Create a components folder in the src .

  1. Sign In component

    Create SignIn.jsx in components

    It will contain a simple form. It will look like this:

     import React, { useState } from "react";
     import { useNavigate } from "react-router-dom";
    
     const SignIn = () => {
       const [credentials, setCredentials] = useState({
         name: "",
         email: "",
         password: "",
         cpassword: "",
       });
       // we will use useNavigate for navigating from one page to another.
       let navigate = useNavigate();
    
       const handleSubmit = async (e) => {
         // logic for signIn
       };
    
       const onChange = (e) => {
         setCredentials({ ...credentials, [e.target.name]: e.target.value });
       };
       return (
         <div className="container my-3 bg-dark text-white rounded p-5">
           <h2>Create An Account</h2>
           <form onSubmit={handleSubmit}>
             <div className="mb-3">
               <label htmlFor="firstName" className="form-label">
                 firstName
               </label>
               <input
                 type="text"
                 className="form-control"
                 id="firstName"
                 name="firstName"
                 onChange={onChange}
                 aria-describedby="firstName"
               />
             </div>
             <div className="mb-3">
               <label htmlFor="lastName" className="form-label">
                 lastName
               </label>
               <input
                 type="text"
                 className="form-control"
                 id="lastName"
                 name="lastName"
                 onChange={onChange}
                 aria-describedby="lastName"
               />
             </div>
             <div className="mb-3">
               <label htmlFor="email" className="form-label">
                 Email address
               </label>
               <input
                 type="email"
                 className="form-control"
                 id="email"
                 name="email"
                 onChange={onChange}
                 aria-describedby="emailHelp"
               />
             </div>
             <div className="mb-3">
               <label htmlFor="password" className="form-label">
                 Password
               </label>
               <input
                 type="password"
                 className="form-control"
                 name="password"
                 id="password"
                 onChange={onChange}
                 minLength={5}
                 required
               />
             </div>
             <div className="mb-3">
               <label htmlFor="cpassword" className="form-label">
                 Confirm Password
               </label>
               <input
                 type="password"
                 className="form-control"
                 id="cpassword"
                 name="cpassword"
                 onChange={onChange}
                 minLength={5}
                 required
               />
             </div>
             <button type="submit" className="btn btn-primary">
               Submit
             </button>
           </form>
         </div>
       );
     };
    
     export default SignIn;
    
  2. Log In component

    Create Login.jsx in components

    It will also contain a simple form. You can write your own logic such that you can use one form for both sign-in and log-in. Login.jsx looks like this:

     import React, { useState } from "react";
     import { useNavigate } from "react-router-dom";
    
     const Login = () => {
       const [credentials, setCredentials] = useState({
         email: "",
         password: "",
       });
       let navigate = useNavigate();
    
       const handleSubmit = async (e) => {
         // logic for login
       };
    
       const onChange = (e) => {
         setCredentials({ ...credentials, [e.target.name]: e.target.value });
       };
       return (
         <div className="container my-3 bg-dark text-white rounded p-5">
           <h2>Login to continue to myNotes</h2>
           <form onSubmit={handleSubmit}>
             <div className="mb-3">
               <label htmlFor="email" className="form-label">
                 Email address
               </label>
               <input
                 type="email"
                 className="form-control"
                 id="email"
                 name="email"
                 value={credentials.email}
                 onChange={onChange}
                 aria-describedby="emailHelp"
               />
             </div>
             <div className="mb-3">
               <label htmlFor="password" className="form-label">
                 Password
               </label>
               <input
                 type="password"
                 className="form-control"
                 name="password"
                 value={credentials.password}
                 onChange={onChange}
                 id="password"
               />
             </div>
             <button type="submit" className="btn btn-primary">
               Submit
             </button>
           </form>
         </div>
       );
     };
    
     export default Login;
    
  3. Navbar component

    Create Navbar.jsx in components

    We will create a navbar here, in which we will have Login, SignIn, and Logout buttons.

     import React from "react";
     import { Link, useNavigate } from "react-router-dom";
    
     const Navbar = () => {
       let navigate = useNavigate();
    
       const handleLogout = () => {
         // logic for logging out
       };
    
       return (
         <nav className="navbar navbar-expand-lg navbar-dark bg-dark">
           <div className="container-fluid">
             <Link className="navbar-brand" to="/">
               myNotes
             </Link>
             <button
               className="navbar-toggler"
               type="button"
               data-bs-toggle="collapse"
               data-bs-target="#navbarSupportedContent"
               aria-controls="navbarSupportedContent"
               aria-expanded="false"
               aria-label="Toggle navigation"
             >
               <span className="navbar-toggler-icon"></span>
             </button>
             <div className="collapse navbar-collapse" id="navbarSupportedContent">
               <ul className="navbar-nav me-auto mb-2 mb-lg-0">
                 <li className="nav-item">
                   <Link className="nav-link" aria-current="page" to="/">
                     Home
                   </Link>
                 </li>
               </ul>
                 <form className="d-flex" role="search">
                   <Link className="btn btn-primary me-1" to="/login" role="button">
                     Login
                   </Link>
                   <Link className="btn btn-primary mx-1" to="/signin" role="button">
                     SignUp
                   </Link>
                 </form>
                 <button onClick={handleLogout} className="btn btn-primary">
                   Log Out
                 </button>
             </div>
           </div>
         </nav>
       );
     };
    
     export default Navbar;
    
  4. Image Component

    Create ImageUpload.jsx in components

    Here we will create a form for uploading images, and at the end, we will write logic for retrieving them from the backend.

     import React, { useEffect, useState } from "react";
    
     const ImageUpload = () => {
       const [image, setImage] = useState(null);
       const [name, setName] = useState("");
       const [imageArray, setImageArray] = useState([]);
       const [loading, setLoading] = useState(false);
    
       const handleFileChange = (e) => {
         setImage(e.target.files[0]);
       };
    
       const handleNameChange = (e) => {
         setName(e.target.value);
       };
    
       const fetchImages = async () => {
         //logic for retrieving image from the backend
       };
    
       useEffect(() => {
         // some logic for fetching images on the initial render
       }, []);
    
       const handleSubmit = async (e) => {
         // logic for uploading image on the backend
       };
    
       return (
         <>
           <div className="container my-3 bg-dark text-white rounded p-5">
             <form onSubmit={handleSubmit}>
               <div className="mb-3">
                 <label htmlFor="name" className="form-label">
                   Name
                 </label>
                 <input
                   type="text"
                   className="form-control"
                   id="name"
                   name="name"
                   onChange={handleNameChange}
                   aria-describedby="name"
                 />
               </div>
               <div className="mb-3">
                 <label htmlFor="image" className="form-label">
                   Upload Image
                 </label>
                 <input
                   type="file"
                   className="form-control"
                   id="image"
                   name="image"
                   accept="image/png,image/jpg,image/jpeg"
                   onChange={handleFileChange}
                 />
               </div>
               <button type="submit" className="btn btn-primary">
                 Submit
               </button>
             </form>
           </div>
           <div className="row my-3">
             <h2 className=" text-white">Your Images</h2>
             <div className="container mx-2 text-white">
               {imageArray.length === 0 && "No Notes to display"}
             </div>
    
             {loading ? (
               <div className="text-white">Loading...</div>
             ) : (
               imageArray.map((image) => (
                 <div key={image._id} className="col-md-4">
                   <div
                     className="card my-3 bg-secondary text-white rounded"
                     style={{ width: "18rem" }}
                   >
                     <img
                       src={image.imageLink}
                       style={{ objectFit: "cover", height: "18rem" }}
                       className="card-img-top"
                       alt="..."
                     />
                     <div className="card-body">
                       <p className="card-text">{image.imageName}</p>
                     </div>
                   </div>
                 </div>
               ))
             )}
           </div>
         </>
       );
     };
    
     export default ImageUpload;
    

    The last thing remaining is establishing the routes on the frontend, for navigating the different components. For that, we have to edit App.jsx file. For creating routes we have to import BrowserRouter, Routes, Route from react-router-dom .

    Run npm i react-router-dom to install the package.

     import "./App.css";
     import Login from "./components/Login";
     import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
     import SignIn from "./components/SignIn";
     import Navbar from "./components/Navbar";
     import ImageUpload from "./components/ImageUpload";
    
     function App() {
       return (
         <>
           <Router>
             <Navbar />
             <div className="container">
               <Routes>
                 <Route path="/" element={<ImageUpload />} />
                 <Route path="/signIn" element={<SignIn />} />
                 <Route path="/login" element={<Login />} />
               </Routes>
             </div>
           </Router>
         </>
       );
     }
    
     export default App;
    

3. Integrating the BackEnd with the FrontEnd

Our web app is almost complete, we have to integrate the backend with the frontend only.

To send the requests or to receive the responses from the backend, we use fetch or axios . For using axios we have to install it, using npm i axios command. fetch is a modern JavaScript API for making HTTP requests. We will use fetch in our project.

  1. Sign-in logic

    Open the SignIn.jsx file.

    sign-in request is post request, so while using fetch API, we have to tell what the method is ( post, put, delete, etc.).

    When we send the request to the backend it will return a token, we will save that token in the localStorage.

    edit the handleSubmit function that we defined earlier in the file.

     const handleSubmit = async (e) => {
         e.preventDefault();
         const { firstName, lastName, email, password } = credentials;
         const response= await fetch("http://localhost:3000/api/v1/user/signUp",
          {
           method: "POST",
           headers: {
             "Content-Type": "application/json",
           },
           body: JSON.stringify({ firstName, lastName, email, password }),
         });
         const json = await response.json();
         console.log(json);
         if (json.success) {
           // save the auth token and redirect
           localStorage.setItem("authorization", json.authtoken);
           navigate("/");
         } else {
           navigate("/signIn");
         }
       };
    
  2. Log-in logic

    Open the Login.jsx file.

    A log-in request is also a post request, so we will do the same as we did for the sign-in logic.

    edit the handleSubmit function in the Login.jsx file

     const handleSubmit = async (e) => {
         e.preventDefault();
         const response = await fetch("http://localhost:3000/api/v1/user/login",
         {
           method: "POST",
           headers: {
             "Content-Type": "application/json",
           },
           body: JSON.stringify({
             email: credentials.email,
             password: credentials.password,
           }),
         });
         const json = await response.json();
         console.log(json);
         if (json.success) {
           localStorage.setItem("authorization", json.token);
           navigate("/");
         } else {
           navigate("/login");
         }
       };
    
  3. Image Uploading and fetching

    This is a bit tricky, but very easy if we understand it once.

    Let's start with the uploading logic, as we were sending the inputs for sign-in and log-in requests in the form of JSON. JSON is an excellent format for sending unstructured data but it can't handle file uploads and that's where the FormData comes into action. FormData allows us to send different types of files. So, what are we waiting for? Let's code...

    Edit the handleSubmit function in the ImageUpload.jsx

     const handleSubmit = async (e) => {
         console.log(e);
         e.preventDefault();
         try {
           setLoading(true);
           // create FormData() instance
           const formData = new FormData();
           // adding values to the formData using append method
           formData.append("image", image);
           formData.append("name", name);
    
           const response = await fetch(
             "http://localhost:3000/api/v1/image/upload",
             {
               method: "POST",
               body: formData,
               headers: {
               Authorization: `Bearer ${localStorage.getItem("authorization")}`,
               },
             }
           );
    
           const json = await response.json();
           setImageArray(imageArray.concat(json.image));
            // resetting the form after submitting
           e.target[1].value = null;
           e.target[0].value = "";
           setImage(null);
           setName("");
           setLoading(false);
         } catch (error) {
           console.log(error);
         }
       };
    

    Image uploading is done, now we have to work on fetching the images from the server.

    When a user is directed to this route, that page must fetch the image on its first rendering and useEffect hook is going to help us.

    Edit the fetchImages function and write the logic for the useEffect, we are going to use fetchImages function inside the useEffect hook.

     const fetchImages = async () => {
         const response = await fetch(
           "http://localhost:3000/api/v1/image/getImages",
           {
             method: "GET",
             headers: {
            Authorization:`Bearer ${localStorage.getItem("authorization")}`,
             },
           }
         );
         const json = await response.json();
         if (json.success) {
           setImageArray(json.images);
         }
       };
    
     useEffect(() => {
         // if user is logged in then fetch the images otherwise
         // redirect the user to the login page
         if (localStorage.getItem("authorization")) {
           fetchImages();
         } else {
           navigate("/login");
         }
       }, []);
    
  4. Fixing the Navbar

    Open the Navbar.jsx file.

    In the Navbar component, we have to write the logic for the logout and it is the easiest of all the logic we have implemented till now. We have to just remove the token from the localStorage.

    Edit the handleLogout Function

     const handleLogout = () => {
         localStorage.removeItem("authorization");
         navigate("/login");
       };
    

    The last thing remaining in the Navbar component is to show and hide the sign-in, log-in, and log-out buttons.

     {
       !localStorage.getItem("authorization") ? (
        <form className="d-flex" role="search">
           <Link className="btn btn-primary me-1" to="/login" role="button">
                Login
            </Link>
            <Link className="btn btn-primary mx-1" to="/signin" role="button">
                 SignUp
            </Link>
         </form>
         ) : (
            <button onClick={handleLogout} className="btn btn-primary">
                  Log Out
            </button>
         )
     }
    

That's it!!!

Our Full-stack application has been made and fully working. The only task on your end is to deploy it, or you can wait for me until I write a blog for that ๐Ÿ˜‚๐Ÿ˜‚๐Ÿ˜‚.

Thank You, and make sure to give feedback and like.

ย