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.
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
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!
Post a Comment