U3F1ZWV6ZTMyMDU3NzIxNTY3NTgxX0ZyZWUyMDIyNDc4MjgyMzU4Mw==

CRUD Operation using React, Redux Toolkit, Node, and MongoDB

This blog post will guide you through the process of creating a CRUD operations app using modern web technology. It combines React and Redux Toolkit for the front end, Express.js for the backend, and MongoDB for the database. For API calls on the front end, Axios is used. You will learn how to build a user-friendly interface for managing employee records and how to effectively handle APIs using createAsyncThunk.

By the end of this blog post, you will have gained the skills necessary to build a robust application that seamlessly integrates React, Redux Toolkit, Express.js, MongoDB, and modern API handling methodologies.


Prerequisites

Before we begin, ensure you have the following tools installed:

  • Node.js (Version 18.16.0)

Setting Up the Backend

Let’s start by setting up the backend using Node.js, Express, and MongoDB. We’ll also use the cors package to handle Cross-Origin Resource Sharing.

Step 1: Setting Up the Project Structure

Create a new directory for your backend and navigate into it:

mkdir backend
cd backend

Step 2: Initializing the Project

Initiate a new Node.js project by running:

npm init -y

Step 3: Installing Dependencies

npm install express mongoose cors nodemon

Step 4: Setting Up the Backend Server

// backend/app.js
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");

const app = express();
app.use(express.json());
app.use(cors());

const connectDB = async () => {
try {
const db = await mongoose.connect("mongodb://127.0.0.1:27017/employee", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log(`mongodb connected ${db.connection.host}`);
} catch (err) {
console.log(err);
process.exit(1);
}
};
connectDB();

const ItemSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
position: {
type: String,
required: true,
},
});
const Item = mongoose.model("Item", ItemSchema);

app.get("/api", (req, res) => {
res.status(200).send({ response: "api worked.." });
});

app.get("/api/items", async (req, res) => {
try {
await Item.find()
.then((response) => {
res.status(200).send({ response: response });
})
.catch((err) => {
res.status(500).send({ response: err.message });
});
} catch (err) {
res.status(500).send({ response: err.message });
}
});

app.post("/api/items", async (req, res) => {
try {
const newItem = new Item({
name: req.body.name,
position: req.body.position,
});
await newItem
.save()
.then((response) => {
res.status(200).send({ response: response });
})
.catch((err) => {
res.status(500).send({ response: err.message });
});
} catch (err) {
res.status(500).send({ response: err.message });
}
});

app.put("/api/items/:id", async (req, res) => {
try {
const updatedItem = await Item.findByIdAndUpdate(req.params.id, req.body, {
new: true,
});
res.status(200).send({ response: updatedItem });
} catch (err) {
res.status(500).send({ response: err.message });
}
});

app.delete("/api/items/:id", async (req, res) => {
try {
await Item.findByIdAndRemove(req.params.id).then((response) => {
res.status(200).send({ response: req.params.id });
});
} catch (err) {
res.status(500).send({ response: err.message });
}
});

app.listen(8000, () => {
console.log(`Server is running on PORT ${8000}`);
});

Step 5: Running the Backend

To run the backend server, execute the following command:

npm start

Your backend server will start running on http://localhost:8000.

Creating the Frontend

We’ll use Vite to scaffold our React frontend project. Run the following commands to create the project:

npm create vite@latest frontend
cd frontend
npm install

Installing Dependencies

npm i axios react-redux @reduxjs/toolkit @mui/material @mui/icons-material

Our front-end project structure will have components, pages, features, and a store. Let’s focus on the essential parts.


Frontend Structure

Setting Up Redux Toolkit Store

// frontend/src/app/store.js
import { configureStore } from "@reduxjs/toolkit";
import employeeSlice from "../feature/employeeSlice";

const store = configureStore({
reducer: {
employeeKey: employeeSlice,
},
});

export default store;

Managing Employee State with Redux Toolkit

// frontend/src/feature/employeeSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

const employeeState = {
updateState: false,
loading: false,
employeeList: [],
error: "",
response: "",
};

export const fetchEmployee = createAsyncThunk(
"employee/fetchEmployee",
async () => {
const response = await axios.get("http://localhost:8000/api/items");
return response.data.response;
}
);

export const addEmployee = createAsyncThunk(
"employee/addEmployee",
async (data) => {
const response = await axios.post("http://localhost:8000/api/items", {
name: data.name,
position: data.position,
});
return response.data.response;
}
);

export const removeEmployee = createAsyncThunk(
"employee/removeEmployee",
async (data) => {
const response = await axios.delete(
`http://localhost:8000/api/items/${data}`
);
return response.data.response;
}
);

export const modifiedEmployee = createAsyncThunk(
"employee/modifiedEmployee",
async (data) => {
const response = await axios.put(
`http://localhost:8000/api/items/${data.id}`,
{
name: data.name,
position: data.position,
}
);
return response.data.response;
}
);

const employeeSlice = createSlice({
name: "employee",
initialState: employeeState,
reducers: {
changeStateTrue: (state) => {
state.updateState = true;
},
changeStateFalse: (state) => {
state.updateState = false;
},
clearResponse: (state) => {
state.response = "";
},
},
extraReducers: (builder) => {
builder
.addCase(addEmployee.pending, (state) => {
state.loading = true;
})
.addCase(addEmployee.fulfilled, (state, action) => {
state.loading = false;
state.employeeList.push(action.payload);
state.response = "add";
})
.addCase(addEmployee.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});

builder
.addCase(fetchEmployee.fulfilled, (state, action) => {
state.employeeList = action.payload;
})
.addCase(fetchEmployee.rejected, (state, action) => {
state.error = action.error.message;
});

builder.addCase(removeEmployee.fulfilled, (state, action) => {
state.employeeList = state.employeeList.filter(
(item) => item._id != action.payload
);
state.response = "delete";
});

builder.addCase(modifiedEmployee.fulfilled, (state, action) => {
const updateItem = action.payload;
console.log(updateItem);
const index = state.employeeList.findIndex(
(item) => item._id === updateItem._id
);
if (index!==-1) {
state.employeeList[index] = updateItem;
}
state.response = "update";
});
},
});

export default employeeSlice.reducer;
export const { changeStateTrue, changeStateFalse, clearResponse } =
employeeSlice.actions;

Creating Employee Management UI

// frontend/src/pages/Home.js
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import {
Alert,
Box,
Button,
Snackbar,
TextField,
Typography,
} from "@mui/material";
import { Edit as EditIcon, Delete as DeleteIcon } from "@mui/icons-material";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
addEmployee,
fetchEmployee,
removeEmployee,
modifiedEmployee,
changeStateTrue,
changeStateFalse,
} from "../feature/employeeSlice";
import { useEffect } from "react";

export default function Home() {
const dispatch = useDispatch();
const { loading, employeeList, error, updateState, response } = useSelector(
(state) => state.employeeKey
);
const [id, setId] = useState("");
const [name, setName] = useState("");
const [position, setPosition] = useState("");

useEffect(() => {
dispatch(fetchEmployee());
}, [dispatch]);

const handleClick = (e) => {
e.preventDefault();
dispatch(
addEmployee({
name: name,
position: position,
})
);
handleClickSnackbar();
setName("");
setPosition("");
};

const updateEmployee = (item) => {
setId(item._id);
setName(item.name);
setPosition(item.position);
dispatch(changeStateTrue());
};

const updateForm = () => {
dispatch(modifiedEmployee({ id: id, name: name, position: position }));
dispatch(changeStateFalse());
handleClickSnackbar();
setId("");
setName("");
setPosition("");
};

const deleteEmployee = (id) => {
dispatch(removeEmployee(id));
handleClickSnackbar();
};

const [open, setOpen] = useState(false);
const handleClickSnackbar = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};

return (
<Box
sx={{
display: "flex",
justifyContent: "center",
mt: 5,
color: "white",
}}
>
<Box
sx={{
width: "55%",
display: "flex",
justifyContent: "center",
flexDirection: "column",
alignItems: "center",
}}
>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-start",
gap: "8px",
}}
>
<TextField
sx={{ color: "white" }}
variant="outlined"
size="small"
placeholder="Name"
value={name}
onChange={(e) => {
setName(e.target.value);
}}
/>
<TextField
variant="outlined"
size="small"
placeholder="Position"
value={position}
onChange={(e) => {
setPosition(e.target.value);
}}
/>
{updateState ? (
<Button
variant="contained"
color="primary"
size="small"
onClick={(e) => {
updateForm(e);
}}
>
Update
</Button>
) : (
<Button
variant="contained"
color="primary"
size="small"
onClick={(e) => {
handleClick(e);
}}
>
Add
</Button>
)}
</Box>
<TableContainer component={Paper} sx={{ marginTop: "16px" }}>
<Table sx={{ minWidth: 659 }} aria-label="simple table">
<TableHead>
<TableRow sx={{ backgroundColor: "black" }}>
<TableCell align="left">
<Typography sx={{ fontWeight: 600, color: "white" }}>
No
</Typography>
</TableCell>
<TableCell align="left">
<Typography sx={{ fontWeight: 600, color: "white" }}>
Name
</Typography>
</TableCell>
<TableCell align="left">
<Typography sx={{ fontWeight: 600, color: "white" }}>
Position
</Typography>
</TableCell>
<TableCell align="left">
<Typography sx={{ fontWeight: 600, color: "white" }}>
Event
</Typography>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{loading ? <TableCell> Loading... </TableCell> : null}
{!loading && employeeList.length == 0 ? (
<TableCell> No Records </TableCell>
) : null}
{!loading && error ? <TableCell> {error} </TableCell> : null}
{employeeList &&
employeeList.map((item, index) => (
<TableRow
key={index}
sx={{
"&:last-child td, &:last-child th": { border: 0 },
}}
>
<TableCell align="left">
<Typography> {index + 1} </Typography>
</TableCell>
<TableCell align="left">
<Typography> {item.name} </Typography>
</TableCell>
<TableCell align="left">
<Typography> {item.position} </Typography>
</TableCell>
<TableCell align="left">
<Box sx={{ display: "flex", cursor: "pointer" }}>
<Box
sx={{ color: "black", mr: 1 }}
onClick={() => updateEmployee(item)}
>
<EditIcon />
</Box>
<Box
sx={{ color: "red" }}
onClick={() => deleteEmployee(item._id)}
>
<DeleteIcon />
</Box>
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>

<Snackbar
open={open}
autoHideDuration={5000}
onClose={handleClose}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert onClose={handleClose} severity="info" sx={{ width: "100%" }}>
{response === "add"
? "employee added successfully"
: response === "delete"
? "employee delete successfully"
: response === "update"
? "employee update successfully"
: null}
</Alert>
</Snackbar>
</Box>
);
}

Integrating Redux and UI in App Component

// frontend/src/App.jsx
import { Box, Typography } from "@mui/material";
import Home from "./pages/Home";

const App = () => {
return (
<Box>
<Box sx={{ display: "flex", justifyContent: "center", mt: 8 }}>
<Typography variant="h5">
CRUD operation using React JS & Redux Toolkit
</Typography>
</Box>
<Home />
</Box>
);
};

export default App;
// frontend/src/main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { Provider } from "react-redux";
import store from "./app/store.jsx";

ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);

Run React Application

To run the frontend, execute the following command:

npm run dev

Your frontend will start running on http://localhost:5173/

Result



CRUD Operation

Conclusion

In this blog, we’ve crafted a complete CRUD app. On the front end, we utilized React and Redux Toolkit to construct a user-friendly interface that seamlessly handles data using `createAsyncThunk` for smooth API interactions. We also benefited from tools like Redux DevTools Extension and Redux Logger for easy debugging. Transitioning to the backend, we employed Express and MongoDB to establish a solid API infrastructure for employee data management. With the `cors` middleware, we enabled secure cross-origin requests, while clearly defined routes facilitated key CRUD actions. By merging these components, we’ve delivered a robust, user-centric app for efficient employee record control.

Feel free to explore further by enhancing the app with additional features or improving the UI design. Happy coding!

تعديل المشاركة
author-img

Anis

Experienced and dedicated Web Developer with a robust skill set honed over two years in the field. Proficient in a range of languages including HTML, CSS, PHP, jQuery, and JavaScript, ensuring a seamless integration of designs and the creation of responsive, user-oriented websites. Specializing in WordPress development, I bring advanced expertise in performance optimization, WordPress security, and content management.
Comments
No comments
Post a Comment

Post a Comment

NameEmailMessage