Home
Blogs
Development

Crafting the Frontend for a MERN-Based User Management System

Development Blog

Crafting the Frontend for a MERN-Based User Management System - Techieonix | Credit: Freepik

Crafting the Frontend for a MERN-Based User Management System

November 28, 2024
10 mins read
Development
Syed Anas Raza
Syed Anas Raza

Full-Stack Developer at Techieonix

After setting up the backend in our last MERN blog, it’s time to bring the application to life by creating the frontend. This blog will guide you through setting up React, designing the user interface, and connecting it with the backend to complete your MERN stack application.

User Management System: Frontend

For building a modern frontend, requires the right tools and a clear process. In this guide, we’ll explore how to create a sleek and interactive user interface using React for component-based development and Tailwind CSS for streamlined styling.

By the end, you’ll have a clear understanding of how to combine these powerful tools to create a frontend that is both functional and visually appealing. Let’s dive into the step-by-step process and bring your project to life.

Building the Frontend with React

To develop frontend, we’d need the same tools and technologies as used in the Backend blog as prerequisites which are:

  • Nodejs

  • NPM (Node Package Manager)

  • VS Code

Since we’ve already setup these in our last backend blog, we can move on to create the react app. If you haven’t installed the above-mentioned requirements, follow the procedure to move further.

  • Set Up the Project

  • Install necessary libraries

  • Set up Tailwind CSS

  • Build UI & Integrate Backend APIs

Step 1: Set Up the Project

First things first, we need to create the base for our React app. Open the my-mern-app directory in VS Code, launch a new terminal, and run the following commands to set up the project:

Copy
npx create-react-app frontend   # Creates react application
cd frontend
npm i
npm start

This will create a fresh React application and launch the development server, allowing you to see your changes in real-time.

Once the app is created and the server is running, the folder structure will look like this:

Copy
└── ParentDirectory/
    ├── frontend/
    │    ├── public
    │        ├── favicon.ico
    │        ├── index.html
    │        ├── logo192.png
    │        ├── logo512.png
    │        ├── manifest.json
    │        └── robots.txt
    │    ├── src
    │        ├── userController.js
    │        ├── App.css
    │        ├── App.js
    │        ├── App.test.js
    │        ├── index.css
    │        ├── index.js
    │        ├── logo.svg
    │        ├── reportWebVitals.js
    │        └── setupTests.js
    │   ├── .gitignore
    │   ├── package-lock.json
    │   ├── package.json
    │   └── README.md
    └── backend

Step 2: Install Necessary Libraries

After building up the react frontend, our application needs a few libraries to make it efficient and dynamic. In this step, we’ll install the following dependencies:

Copy
npm i axios react-router-dom
  • axios : A promise-based HTTP client for making requests to APIs.

  • react-router-dom : For handling routing and navigation in React applications.

Your package.json file will look something like this after the installation:

package.json file within frontend folder - Techieonix

package.json file within frontend folder - Techieonix

Step 3: Set up Tailwind CSS

To make our application look modern and responsive, we’ll use Tailwind CSS, a utility-first CSS framework. Tailwind helps us rapidly create custom designs without writing custom CSS from scratch.

  • Visit tailwindcss.com & copy the CDN mentioned in step 1.

  • Paste the CDN link into the <head> section of public/index.html.

With Tailwind now integrated, we can start styling our pages with ease.

Step 4: Build UI & Integrate Backend APIs

In our User Management System, we will need three core pages: Login, Home, and User Page. These pages will work together to allow users to login, view a list of users, and perform CRUD (Create, Read, Update, Delete) operations on user data.

Login Page

In login page, users will enter their credentials to authenticate themselves. To create this page:

  • Create a pages folder in the src directory.

  • Inside the pages folder, create a file named Login.jsx.

Copy
/* /src/pages/Login.jsx */
import React, { useState } from 'react';
import { useNavigate } from "react-router-dom";
import axios from 'axios'

const Login = () => {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [error, setError] = useState("");
    const [showPassword, setShowPassword] = useState(false);
    const navigate = useNavigate();

    const handleSubmit = async (e) => {
        e.preventDefault();
        axios.post("http://localhost:5000/api/login", { email, password }).then(res => {
            console.log('User added successfully:', res);
            localStorage.setItem("token", res.data.token);
            localStorage.setItem("user", JSON.stringify(res.data.user));
            navigate("/")
        }).catch(err => {
            console.error(err);
            setError(err?.response?.data);
        })
    }

    return (
        <div className="min-h-screen flex items-center justify-center bg-gray-100">
            <div className="w-full max-w-md p-8 space-y-6 bg-white shadow-lg rounded-lg">
                <h2 className="text-2xl font-bold text-center">Login</h2>

                {error && <p className="text-red-500 text-center">{error}</p>}

                <form onSubmit={handleSubmit} className="flex flex-col gap-4">
                    <input
                        type="email"
                        value={email}
                        onChange={(e) => setEmail(e.target.value)}
                        placeholder="Email"
                        className="border border-gray-300 rounded px-4 py-2"
                        required
                    />
                    <div className="relative">
                        <input
                            type={showPassword ? "text" : "password"}
                            value={password}
                            onChange={(e) => setPassword(e.target.value)}
                            placeholder="Password"
                            className="border border-gray-300 rounded px-4 py-2 w-full"
                            required
                        />
                        <span
                            onClick={() => setShowPassword(!showPassword)}
                            className={`absolute right-2 top-2 text-blue-500 font-bold cursor-pointer ${showPassword && "bg-blue-100"} p-1 text-sm select-none`}
                        >
                            Show
                        </span>
                    </div>
                    <button
                        type="submit"
                        className="bg-blue-500 text-white rounded py-2 mt-4 hover:bg-blue-600"
                    >
                        Log In
                    </button>
                </form>

            </div>
        </div>
    )
}
export default Login;

The Login component will:

  • Manage State: Using React’s useState, we’ll store the email, password, error messages, and visibility toggle for the password field.

  • Handle Navigation: Using useNavigate, we’ll redirect the user to the homepage after a successful login.

  • Submit Function: The form data will be sent to the backend using Axios. On success, we’ll store the JWT token and user details in local storage and navigate to the homepage. If there’s an error, we will display an appropriate error message.

With this flow in place, the login page will be fully functional, enabling users to log in securely and navigate to the app.

Home Page

After login the user is navigated to the home page, which is the heart of user management, where admins can efficiently manage the users of the system.

  • Create a file named Home.jsx in pages folder.

Copy
/* /src/pages/Home.jsx */
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom';
import axios from 'axios';

const Home = () => {
    const [users, setUsers] = useState(null);
    const [error, setError] = useState("");

    useEffect(() => getUsers(), []);

    const getUsers = () => {
        axios.get("http://localhost:5000/api/users", {
            headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
        }).then(res => {
            console.log(res.data);
            setUsers(res.data.users);
        }).catch(err => {
            console.error(err);
            setUsers([]);
            setError(err?.response?.data);
        })
    }

    const deleteUser = (id) => {
        axios.delete(`http://localhost:5000/api/users/${id}`, {
            headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
        }).then(res => {
            console.log(res.data);
            getUsers();
        }).catch(err => {
            console.error(err);
            setUsers([]);
            setError(err?.response?.data);
        })
    }

    return (
        <main className="min-h-screen bg-gray-100 p-10">
            <h1 className="text-3xl font-bold text-center mb-6">User Management</h1>
            <div className="max-w-2xl mx-auto bg-white shadow-lg rounded-lg p-6">
                <div className="flex justify-end gap-x-5">
                    <Link to="/login" className="bg-blue-500 hover:bg-blue-600 text-white px-3 py-2 rounded">Login</Link>
                    <Link to="/user" className="bg-green-500 hover:bg-green-600 text-white px-3 py-2 rounded">Add user</Link>
                </div>

                {error && <p className="text-red-500 text-center">{error}</p>}

                <table className="min-w-full bg-white mt-6">
                    <thead>
                        <tr>
                            <th className="py-2 px-4 border-b">Name</th>
                            <th className="py-2 px-4 border-b">Email</th>
                            <th className="py-2 px-4 border-b">Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        {users?.map(user => (
                            <tr key={user._id} className="hover:bg-gray-100 cursor-pointer">
                                <td className="py-2 px-4 border-b">{user.name}</td>
                                <td className="py-2 px-4 border-b">{user.email}</td>
                                <td className="py-2 px-4 border-b text-center">
                                    <Link to={`/user?updateUser=${user._id}`}
                                        className="bg-yellow-500 hover:bg-yellow-600 text-white px-3 py-1 rounded mr-2"
                                    >
                                        Edit
                                    </Link>
                                    <button
                                        onClick={() => deleteUser(user._id)}
                                        className="bg-red-500 hover:bg-red-600 text-white px-3 py-1 rounded"
                                    >
                                        Delete
                                    </button>
                                </td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </div>
        </main>
    )
}
export default Home;

The home page displays a list of all users in a dynamic table.

  • State Management: users's state stores users' data, and error's state handles errors using useState.

  • Data Fetching: On load, useEffect calls getUsers, which fetches users from an API with authorization that we set in the backend.

  • UI: Renders a table of users with actions:

    • Edit User: Links to the user page with the user ID as a query parameter for further editing via a PUT request.

    • Delete User: Calls deleteUser function which sends a delete request for a user by ID, refreshing the user list afterward.

User Page

This page is used for both adding and editing user information. Whether we’re creating a new user or modifying an existing one, the process will be handled seamlessly by this page.

  • Create a file named User.jsx in pages folder for both adding and updating user information.

Copy
/* /src/pages/User.jsx */
import React, { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom';
import axios from 'axios';

const User = () => {
    const [user, setUser] = useState({
        name: "",
        email: "",
        password: "",
        role: "",
        dob: "",
        phone: "",
        address: ""
    });
    const [showPassword, setShowPassword] = useState(false);
    const [error, setError] = useState("");
    const navigate = useNavigate();
    const { search } = useLocation();

    useEffect(() => {
        const userId = new URLSearchParams(search).get("updateUser");
        axios.get(`http://localhost:5000/api/users/${userId}`, {
            headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
        }).then(res => {
            console.log(res.data);
            setUser(res.data);
        }).catch(err => console.error(err));
    }, [search]);

    const handleSubmit = e => {
        e.preventDefault();
        axios.post("http://localhost:5000/api/users", user, {
            headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
        }).then(res => {
            console.log(res.data);
            navigate("/");
        }).catch(err => {
            console.error(err);
            setError(err?.response?.data);
        })
    }
    const handleUpdate = e => {
        e.preventDefault();
        const userId = new URLSearchParams(search).get("updateUser");
        axios.put(`http://localhost:5000/api/users/${userId}`, user, {
            headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
        }).then(res => {
            console.log(res.data);
            navigate("/");
        }).catch(err => {
            console.error(err);
            setError(err?.response?.data);
        })
    }

    return (
        <div className="min-h-screen flex items-center justify-center bg-gray-100">
            <div className="w-full max-w-lg p-8 space-y-6 bg-white shadow-lg rounded-lg">
                <h2 className="text-2xl font-bold text-center">Add New User</h2>

                {error && <p className="text-red-500 text-center">{error}</p>}

                <form onSubmit={user ? handleUpdate : handleSubmit} className="flex flex-col gap-4">
                    <input
                        type="text"
                        name="name"
                        value={user?.name}
                        onChange={e => setUser({ ...user, name: e.target.value })}
                        placeholder="John Doe*"
                        className="border border-gray-300 rounded px-4 py-2"
                        required
                    />
                    <input
                        type="email"
                        name="email"
                        value={user?.email}
                        onChange={e => setUser({ ...user, email: e.target.value })}
                        placeholder="john@example.com*"
                        className="border border-gray-300 rounded px-4 py-2"
                        required
                    />
                    {!user && <div className="relative">
                        <input
                            type={showPassword ? "text" : "password"}
                            name="password"
                            value={user?.password}
                            onChange={e => setUser({ ...user, password: e.target.value })}
                            placeholder="helloWorld*"
                            className="border border-gray-300 rounded px-4 py-2 w-full"
                            required
                        />
                        <span
                            onClick={() => setShowPassword(!showPassword)}
                            className="absolute right-2 top-2 text-blue-500 font-bold cursor-pointer hover:bg-blue-100 p-1 text-sm select-none"
                        >
                            {showPassword ? "Hide" : "Show"}
                        </span>
                    </div>}
                    <select
                        name="role"
                        value={user?.role}
                        onChange={e => setUser({ ...user, role: e.target.value })}
                        className="border border-gray-300 rounded px-4 py-2"
                        required
                    >
                        <option value="">Select Role</option>
                        <option value="Admin">Admin</option>
                        <option value="User">User</option>
                        <option value="Manager">Manager</option>
                    </select>
                    <input
                        type="date"
                        name="dob"
                        value={user?.dob}
                        onChange={e => setUser({ ...user, dob: e.target.value })}
                        placeholder="Date of Birth"
                        className="border border-gray-300 rounded px-4 py-2"
                    />
                    <input
                        type="text"
                        name="phone"
                        value={user?.phone}
                        onChange={e => setUser({ ...user, phone: e.target.value })}
                        placeholder="+923111234567"
                        className="border border-gray-300 rounded px-4 py-2"
                    />
                    <textarea
                        name="address"
                        value={user?.address}
                        onChange={e => setUser({ ...user, address: e.target.value })}
                        placeholder="Flat # 1, abc apartment, xyz street, Pakistan"
                        className="border border-gray-300 rounded px-4 py-2"
                    />
                    <button
                        type="submit"
                        className="bg-blue-500 text-white rounded py-2 mt-4 hover:bg-blue-600"
                    >
                        {user ? "Update" : "Add User"}
                    </button>
                </form>
            </div>
        </div>
    )
}
export default User;

Here’s a breakdown of the User page

  • State Management: Uses useState to handle user data, password visibility (showPassword), error messages (error), and update mode (isUpdating).

  • Routing: Retrieves query parameters with useLocation, and uses useNavigate to redirect after form submission.

  • Data Fetching: On load, useEffect checks for a userId in the URL to decide if it should fetch and load user data for editing. Requests include an authorization token from localStorage.

  • Form Submission: handleSubmit adds a new user via POST, and handleUpdate modifies an existing user with PUT, both redirecting on success.

  • Button Text: Toggles button text between "Add User" and "Update" based on isUpdating.

  • Error Handling: Displays API errors in error.

Step 5: Set up Routes

With all our pages set up, it’s time to wire them together. Open the src/App.js file and configure the routes using react-router-dom:

Copy
/* /src/App.js */
import React from 'react';
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from './pages/Home';
import Login from './pages/Login';
import User from './pages/User';

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route path="/user" element={<User />} />
      </Routes>
    </BrowserRouter>
  )
}

export default App

With the core pages and components in frontend are all set up, you’re ready to move forward to finishing touches and testing your application.

Run and Test the Application

After building the MERN stack application, it’s time to see everything in action! In this section, we’ll go through the steps to run both the frontend and backend servers, test the functionalities, and manage users via the admin panel. So, let’s get started.

Start the Servers

The first step in testing our application is to start both the frontend and backend servers. Here’s how to do it:

  • Start the Frontend Server: In a terminal window, navigate to the frontend directory and run the following command to launch the React development server.

Copy
npm start
  • Start the Backend Server: Open a new terminal window, navigate to the backend directory, and start the backend server by running.

Copy
node app.js

Once the frontend is started, it'll generate a URL (like http://localhost:3000), browse the URL in the browser. The interface will look like as shown in screenshot 1

Screenshot 1 - Home page - Techieonix

Screenshot 1 - Home page - Techieonix

We created an admin user in our previous article. Visit this link to learn how we set up a default admin user.

Log in to the Application

Log in with the admin credentials as shown in screenshot 2. You should now have access to the full range of features, including user management, as an admin.

Screenshot 2 - Login screen - Techieonix

Screenshot 2 - Login screen - Techieonix

Add User

Now that you’re logged in, you can begin adding users to the system. Simply click the "Add User" button on the homepage to open the user creation form. Fill in the necessary user details (e.g., name, email, password) and click "Add User" button to create the new user. Refer to screenshot 3.

Screenshot 3 - Add new user page - Techieonix

Screenshot 3 - Add new user page - Techieonix

Once the new user is added, you will be redirected back to the homepage, where the user will appear in the list.

Screenshot 4 - Users list - Techieonix

Screenshot 4 - Users list - Techieonix

Delete User

Creating a new user will redirect you back to the home page. As an admin, you can delete any of the existing users by clicking the Delete button next to their names, as shown in screenshot 5.

Screenshot 5 - Delete user - Techieonix

Screenshot 5 - Delete user - Techieonix

Edit User

To edit a user’s details, click the Edit button next to the user you wish to modify. This will take you to the user form, pre-filled with the current information. Make the necessary changes and click the Update button to save the modifications, as shown in screenshot 6.

Screenshot 6 - Edit User - Techieonix

Screenshot 6 - Edit User - Techieonix

Once updated, the user’s details will be reflected immediately on the homepage, and they will be stored in the database.

Ready to build a scalable and efficient backend for your next project?

At Techieonix, we specialize in creating robust backend systems, seamless API integrations, and scalable full-stack solutions using the MERN stack. Whether you’re starting from scratch or looking to enhance your existing system, we can help you bring your vision to life.

Let’s make it happen! Get in touch with us today to discuss how we can turn your ideas into reality.
Let’s Discuss

Troubleshooting

  • Common Database Issues

    • Problem: MongoDB connection errors

      Solution: Check the connection URI, MongoDB server status, and IP whitelisting for MongoDB Atlas.

    • Problem: Data not saving correctly

      Solution: Ensure Mongoose schemas match the data structure, and check for validation errors.

  • API and Server Issues

    • Problem: Backend not responding

      Solution: Verify the server is running on the correct port, and check API endpoints for typos or route mismatches.

  • Frontend Issues

    • Problem: Unable to fetch data from API

      Solution: Check if the API endpoint is correct and if the backend server is running. Confirm that HTTP methods match (e.g., GET, POST).

    • Problem: State not updating correctly; for e.g, the user is not being removed from the screen after deletion until the page is reloaded.

      Solution: Verify if you’re using state management hooks or libraries correctly and check for unnecessary re-renders.

  • Authentication Problems

    • Problem: JWT token not stored correctly

      Solution: Ensure the token is saved in localStorage or cookies after login and sent in headers properly for protected routes.

    • Problem: Unauthorized access errors on protected routes

      Solution: Double-check middleware and verify the user’s role or token validity.

FAQ

  • How do I set up the environment variables?

    Create a .env file at the root of backend folder, add variables (like DB_URI for the database connection), and access them in the code using process.env.

  • Why do I need both a backend and a frontend folder?

    This separation keeps concerns isolated: the backend handles data processing and API logic, while the frontend focuses on user interaction and display.

  • How do I debug errors in my API calls?

    Use console.log statements, browser developer tools, or Postman to check API request and response payloads. Make sure endpoints and request methods match.

  • Can I deploy this app as-is to production?

    For production, optimize by handling environment variables securely, applying rate limiting, securing sensitive data, ensuring HTTPS in your deployment setup, and replacing your local URLs http://localhost:PORT with your production domain.

Mission Accomplished ✅

We have successfully completed both the backend and frontend of our User Management System, built using the MERN stack. With these skills, you're ready to create professional-grade and full-stack applications. 🚀

🌟 Subscribe to our newsletter to stay updated on the latest information in technology, AI, and more!

Syed Anas Raza
Syed Anas Raza
Full-Stack Developer at Techieonix

Crafting the Frontend for a MERN-Based User Management System

November 28, 2024

10 mins read
Development

Share Link

Share

Our Latest Blog

Stay updated with the latest trends, insights, and news from the IT industry. Our blog features articles, guides, and expert opinions to help you stay informed and ahead of the curve

View All Blogs

Looking for more digital insights?

Get our latest blog posts, research reports, and thought leadership right in your inbox each month.

Follow Us here

Lets connect and explore possibilities

Ready to transform your business with innovative IT solutions? Get in touch with us today. Our team is here to answer your questions and discuss how we can help you achieve your goals.