In this tutorial, we will build a simple CRUD application using React as the frontend and Spring Boot as the backend. We will use the MySQL database to store and retrieve the data.
In this tutorial, we are going to use the latest version of React 18+ and Spring Boot version 3+.
Spring Boot CRUD REST APIs - Backend Implementation
Add Maven Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
Configure MySQL Database
spring.datasource.url=jdbc:mysql://localhost:3306/ems?useSSL=false
spring.datasource.username=root
spring.datasource.password=Mysql@123
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto = update
Create JPA Entity
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import jakarta.persistence.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "email_id")
private String email;
}
Create Spring Data JPA Repository
import net.javaguides.springboot.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
// all crud database methods
}
Create EmployeeDto and EmployeeMapper Classes
EmployeeDto
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class EmployeeDto {
private long id;
private String firstName;
private String lastName;
private String email;
}
EmployeeMapper Class
import net.javaguides.ems.dto.EmployeeDto;
import net.javaguides.ems.entity.Employee;
public class EmployeeMapper {
public static EmployeeDto mapToEmployeeDto(Employee employee){
return new EmployeeDto(
employee.getId(),
employee.getFirstName(),
employee.getLastName(),
employee.getEmail()
);
}
public static Employee mapToEmployee(EmployeeDto employeeDto){
return new Employee(
employeeDto.getId(),
employeeDto.getFirstName(),
employeeDto.getLastName(),
employeeDto.getEmail()
);
}
}
Service Layer
EmployeeService Interface
import net.javaguides.springboot.dto.EmployeeDto;
import java.util.List;
public interface EmployeeService {
List<EmployeeDto> getAllEmployees();
EmployeeDto createEmployee(EmployeeDto employee);
EmployeeDto getEmployeeById(Long employeeId);
EmployeeDto updateEmployee(Long employeeId, EmployeeDto employeeDto);
void deleteEmployee(Long employeeId);
}
EmployeeServiceImpl Class
import lombok.AllArgsConstructor;
import net.javaguides.springboot.dto.EmployeeDto;
import net.javaguides.springboot.exception.ResourceNotFoundException;
import net.javaguides.springboot.mapper.EmployeeMapper;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.repository.EmployeeRepository;
import net.javaguides.springboot.service.EmployeeService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
public class EmployeeServiceImpl implements EmployeeService {
private EmployeeRepository employeeRepository;
@Override
public List<EmployeeDto> getAllEmployees() {
List<Employee> employees = employeeRepository.findAll();
List<EmployeeDto> employeeDtos = employees.stream()
.map((employee) -> EmployeeMapper.mapToEmployeeDto(employee))
.collect(Collectors.toList());
return employeeDtos;
}
@Override
public EmployeeDto createEmployee(EmployeeDto employeeDto) {
Employee employee = EmployeeMapper.mapToEmployee(employeeDto);
Employee savedEmployee = employeeRepository.save(employee);
return EmployeeMapper.mapToEmployeeDto(savedEmployee);
}
@Override
public EmployeeDto getEmployeeById(Long employeeId) {
Employee employee = employeeRepository.findById(employeeId)
.orElseThrow(() ->
new ResourceNotFoundException("Employee not exist with id: " + employeeId));
EmployeeDto employeeDto = EmployeeMapper.mapToEmployeeDto(employee);
return employeeDto;
}
@Override
public EmployeeDto updateEmployee(Long employeeId, EmployeeDto employeeDto) {
Employee existingEmployee = employeeRepository.findById(employeeId)
.orElseThrow(() ->
new ResourceNotFoundException("Employee not exist with id: " + employeeId));
existingEmployee.setFirstName(employeeDto.getFirstName());
existingEmployee.setLastName(employeeDto.getLastName());
existingEmployee.setEmail(employeeDto.getEmail());
employeeRepository.save(existingEmployee);
return EmployeeMapper.mapToEmployeeDto(existingEmployee);
}
@Override
public void deleteEmployee(Long employeeId) {
Employee existingEmployee = employeeRepository.findById(employeeId)
.orElseThrow(() ->
new ResourceNotFoundException("Employee not exist with id: " + employeeId));
employeeRepository.deleteById(employeeId);
}
}
Controller Layer
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException{
public ResourceNotFoundException(String message){
super(message);
}
}
import lombok.AllArgsConstructor;
import net.javaguides.springboot.dto.EmployeeDto;
import net.javaguides.springboot.exception.ResourceNotFoundException;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.repository.EmployeeRepository;
import net.javaguides.springboot.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin("*")
@AllArgsConstructor
@RestController
@RequestMapping("/api/v1/employees")
public class EmployeeController {
private EmployeeService employeeService;
@GetMapping
public ResponseEntity<List<EmployeeDto>> getAllEmployees(){
List<EmployeeDto> employees = employeeService.getAllEmployees();
return ResponseEntity.ok(employees);
}
// build create employee REST API
@PostMapping
public ResponseEntity<EmployeeDto> createEmployee(@RequestBody EmployeeDto employee) {
EmployeeDto employeeDto = employeeService.createEmployee(employee);
return new ResponseEntity<>(employeeDto, HttpStatus.CREATED);
}
// build get employee by id REST API
@GetMapping("{id}")
public ResponseEntity<EmployeeDto> getEmployeeById(@PathVariable("id") Long employeeId){
EmployeeDto employee = employeeService.getEmployeeById(employeeId);
return ResponseEntity.ok(employee);
}
// build update employee REST API
@PutMapping("{id}")
public ResponseEntity<EmployeeDto> updateEmployee(@PathVariable("id") Long employeeId,
@RequestBody EmployeeDto employeeDetails) {
EmployeeDto updateEmployee = employeeService.updateEmployee(employeeId, employeeDetails);
return ResponseEntity.ok(updateEmployee);
}
// build delete employee REST API
@DeleteMapping("{id}")
public ResponseEntity<String> deleteEmployee(@PathVariable("id") Long employeeId){
employeeService.deleteEmployee(employeeId);
return ResponseEntity.ok("Employee deleted successfully!");
}
}
React JS CRUD Application - Frontend Implementation
Create a React UI with Create React App
npx create-react-app ems-frontend
package.json - The package.json file contains all the required dependencies for our React JS project. Most importantly, you can check the current version of React that you are using. It has all the scripts to start, build, and eject our React app.
public folder - The public folder contains index.html. As React is used to build a single-page application, we have this single HTML file to render all our components. Basically, it's an HTML template. It has a div element with id as root and all our components are rendered in this div with index.html as a single page for the complete react app.
src folder- In this folder, we have all the global javascript and CSS files. All the different components that we will be building, sit here.
index.js - This is the top renderer of your React app.
node_modules - All the packages installed by NPM or Yarn will reside inside the node_modules folder.
App.js - The App.js file contains the definition of our App component which actually gets rendered in the browser and this is the root component.
package.json
This file contains all the required dependencies for our React JS project. Most importantly, you can check the current version of React that you are using. It has all the scripts to start, build, and eject our React app:{
"name": "react-todo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.3.6",
"bootstrap": "^5.2.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.10.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Adding Bootstrap in React Using NPM
Open a new terminal window, navigate to your project's folder, and run the following command:
$ npm install bootstrap --save
--save option add an entry in the package.json file
Open the src/index.js file and add the following code:
import 'bootstrap/dist/css/bootstrap.min.css';
Complete code index.js:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.min.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
EmployeeService - Consume CRUD REST APIs
npm add axios --save
import axios from 'axios'
const EMPLOYEE_BASE_REST_API_URL = 'http://localhost:8080/api/v1/employees';
export const listEmployees = () => {
return axios.get(EMPLOYEE_BASE_REST_API_URL)
};
export const createEmployee = (employee) => {
return axios.post(EMPLOYEE_BASE_REST_API_URL, employee)
}
export const getEmployeeById = (employeeId) => {
return axios.get(EMPLOYEE_BASE_REST_API_URL + '/' + employeeId);
}
export const updateEmployee = (employeeId, employee) => {
return axios.put(EMPLOYEE_BASE_REST_API_URL + '/' +employeeId, employee);
}
export const deleteEmployee = (employeeId) => {
return axios.delete(EMPLOYEE_BASE_REST_API_URL + '/' + employeeId);
}
React List Employee Component
In this section, we will create a new folder called components inside the src folder. Then create a new file called ListEmployeeComponent,js.import React, {useState, useEffect} from 'react'
import {listEmployees} from '../services/EmployeeService'
const ListEmployeeComponent = () => {
const [employees, setEmployees] = useState([])
useEffect(() => {
getAllEmployees();
}, [])
const getAllEmployees = () => {
listEmployees().then((response) => {
setEmployees(response.data)
console.log(response.data);
}).catch(error =>{
console.log(error);
})
}
return (
<div className = "container">
<h2 className = "text-center"> List Employees </h2>
<table className="table table-bordered table-striped">
<thead>
<th> Employee Id </th>
<th> Employee First Name </th>
<th> Employee Last Name </th>
<th> Employee Email Id </th>
</thead>
<tbody>
{
employees.map(
employee =>
<tr key = {employee.id}>
<td> {employee.id} </td>
<td> {employee.firstName} </td>
<td>{employee.lastName}</td>
<td>{employee.email}</td>
</tr>
)
}
</tbody>
</table>
</div>
)
}
export default ListEmployeeComponent
The useEffect Hook allows us to perform side effects (an action) in the function components. It does not use component lifecycle methods that are available in class components. Side effects have common features which most web applications need to perform, such as:
- Updating the DOM,
- Fetching and consuming data from a server API,
- Setting up a subscription, etc.
<tbody>
{
employees.map(
employee =>
<tr key = {employee.id}>
<td> {employee.id} </td>
<td> {employee.firstName} </td>
<td>{employee.lastName}</td>
<td>{employee.email}</td>
</tr>
)
}
</tbody>
HeaderComponent and FooterComponent
import React from 'react'
const HeaderComponent = () => {
return (
<div>
<header>
<nav className = "navbar navbar-expand-md navbar-dark bg-dark">
<div>
<a href = "https://javaguides.net" className = "navbar-brand">
Employee Management Application
</a>
</div>
</nav>
</header>
</div>
)
}
export default HeaderComponent
import React from 'react'
const FooterComponent = () => {
return (
<div>
<footer className = "footer">
<span className="text-muted">All Rights Reserved 2021 @JavaGuides</span>
</footer>
</div>
)
}
export default FooterComponent
.footer {
position: absolute;
bottom: 0;
width:100%;
height: 50px;
background-color: black;
text-align: center;
color: white;
}
Update App.js to use FooterComponent and Test
import './App.css';
import FooterComponent from './components/FooterComponent';
import HeaderComponent from './components/HeaderComponent';
import ListEmployeeComponent from './components/ListEmployeeComponent';
function App() {
return (
<div>
<HeaderComponent />
<ListEmployeeComponent />
<FooterComponent />
</div>
);
}
export default App;
Configure Routing in React App
To use React Router, you first have to install it using NPM:
npm install react-router-dom --save
Next, let's configure Routing in the App Component:
import './App.css';
import {BrowserRouter, Routes, Route } from 'react-router-dom';
import FooterComponent from './components/FooterComponent';
import HeaderComponent from './components/HeaderComponent';
import ListEmployeeComponent from './components/ListEmployeeComponent';
function App() {
return (
<div>
<BrowserRouter>
<HeaderComponent />
<div className= "container">
<Routes>
<Route path = "/" element = { <ListEmployeeComponent /> }></Route>
<Route path = "/employees" element = { <ListEmployeeComponent /> }></Route>
</Routes>
</div>
<FooterComponent />
</BrowserRouter>
</div>
);
}
export default App;
Add and Update Employee Component
import React, {useState, useEffect} from 'react'
import {useNavigate, useParams } from 'react-router-dom';
import { updateEmployee, createEmployee, getEmployeeById} from '../services/EmployeeService';
const EmployeeComponent = () => {
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [email, setEmail] = useState('')
const navigate = useNavigate();
const {id} = useParams();
const saveOrUpdateEmployee = (e) => {
e.preventDefault();
const employee = {firstName, lastName, email, departmentId}
console.log(employee);
if(id){
updateEmployee(id, employee).then((response) => {
navigate('/employees')
}).catch(error => {
console.log(error)
})
}else{
createEmployee(employee).then((response) =>{
console.log(response.data)
navigate('/employees');
}).catch(error => {
console.log(error)
})
}
}
useEffect(() => {
if(id){
getEmployeeById(id).then((response) =>{
setFirstName(response.data.firstName)
setLastName(response.data.lastName)
setEmail(response.data.email)
}).catch(error => {
console.log(error)
})
}
}, [id])
const pageTitle = () => {
if(id){
return <h2 className = "text-center">Update Employee</h2>
}else{
return <h2 className = "text-center">Add Employee</h2>
}
}
return (
<div>
<br /><br />
<div className = "container">
<div className = "row">
<div className = "card col-md-6 offset-md-3 offset-md-3">
{
pageTitle()
}
<div className = "card-body">
<form>
<div className = "form-group mb-2">
<label className = "form-label"> First Name :</label>
<input
type = "text"
placeholder = "Enter first name"
name = "firstName"
className = "form-control"
value = {firstName}
onChange = {(e) => setFirstName(e.target.value)}
>
</input>
</div>
<div className = "form-group mb-2">
<label className = "form-label"> Last Name :</label>
<input
type = "text"
placeholder = "Enter last name"
name = "lastName"
className = "form-control"
value = {lastName}
onChange = {(e) => setLastName(e.target.value)}
>
</input>
</div>
<div className = "form-group mb-2">
<label className = "form-label"> Email Id :</label>
<input
type = "email"
placeholder = "Enter email Id"
name = "email"
className = "form-control"
value = {email}
onChange = {(e) => setEmail(e.target.value)}
>
</input>
</div>
<button className = "btn btn-success" onClick = {(e) => saveOrUpdateEmployee(e)} >Submit </button>
{/* <Link to="/employees" className="btn btn-danger"> Cancel </Link> */}
</form>
</div>
</div>
</div>
</div>
</div>
)
}
export default EmployeeComponent
We used useState() hook to create state variables in our functional components:
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [email, setEmail] = useState('')
const {id} = useParams();
const saveOrUpdateEmployee = (e) => {
e.preventDefault();
const employee = {firstName, lastName, email, departmentId}
console.log(employee);
if(id){
updateEmployee(id, employee).then((response) => {
navigate('/employees')
}).catch(error => {
console.log(error)
})
}else{
createEmployee(employee).then((response) =>{
console.log(response.data)
navigate('/employees');
}).catch(error => {
console.log(error)
})
}
}
const navigate = useNavigate();
Navigate to /employees page:
navigate('/employees')
Update The Routing for EmployeeComponent
import './App.css';
import {BrowserRouter, Routes, Route } from 'react-router-dom';
import FooterComponent from './components/FooterComponent';
import HeaderComponent from './components/HeaderComponent';
import ListEmployeeComponent from './components/ListEmployeeComponent';
import EmployeeComponent from './components/EmployeeComponent';
function App() {
return (
<div>
<BrowserRouter>
<HeaderComponent />
<div className= "container">
<Routes>
<Route path = "/" element = { <ListEmployeeComponent /> }></Route>
<Route path = "/employees" element = { <ListEmployeeComponent /> }></Route>
<Route path = "/add-employee" element = { <EmployeeComponent />} ></Route>
<Route path = "/edit-employee/:id" element = { <EmployeeComponent />}></Route>
</Routes>
</div>
<FooterComponent />
</BrowserRouter>
</div>
);
}
export default App;
Delete Employee Feature - Frontend
- Add delete button to list employees table:
<button className = "btn btn-danger" onClick = {() => removeEmployee(employee.id)}
style = {{marginLeft:"10px"}}> Delete</button>
- Next, create removeEmployee function and print the id:
const removeEmployee = (employeeId) => {
console.log(id);
}
- Next, let's write the code to make delete employee REST API using Axios:
export const deleteEmployee = (employeeId) => {
return axios.delete(EMPLOYEE_BASE_REST_API_URL + '/' + employeeId);
}
- Change removeEmployee to call deleteEmployee method:
const removeEmployee = (employeeId) => {
deleteEmployee(employeeId).then((response) =>{
getAllEmployees();
}).catch(error =>{
console.log(error);
})
}
import React, {useState, useEffect} from 'react'
import { useNavigate } from 'react-router-dom'
import {listEmployees, deleteEmployee} from '../services/EmployeeService'
const ListEmployeeComponent = () => {
const [employees, setEmployees] = useState([])
const navigate = useNavigate()
useEffect(() => {
getAllEmployees();
}, [])
const getAllEmployees = () => {
listEmployees().then((response) => {
setEmployees(response.data)
console.log(response.data);
}).catch(error =>{
console.log(error);
})
}
const removeEmployee = (employeeId) => {
deleteEmployee(employeeId).then((response) =>{
getAllEmployees();
}).catch(error =>{
console.log(error);
})
}
function addNewEmployee() {
navigate('/add-employee')
}
const updateEmployee = (id) => {
navigate(`/edit-employee/${id}`)
}
return (
<div className = "container">
<br /><br />
<h2 className = "text-center"> List Employees </h2>
{/* <Link to = "/add-employee" className = "btn btn-primary mb-2" > Add Employee </Link> */}
<button className = "btn btn-primary mb-2" onClick={addNewEmployee }>Add Employee</button>
<table className="table table-bordered table-striped">
{/* <thead className="table-dark"> */}
<thead>
<tr>
<th> Employee Id </th>
<th> Employee First Name </th>
<th> Employee Last Name </th>
<th> Employee Email Id </th>
<th> Department Name</th>
<th> Actions </th>
</tr>
</thead>
<tbody>
{
employees.map(
employee =>
<tr key = {employee.id}>
<td> {employee.id} </td>
<td> {employee.firstName} </td>
<td>{employee.lastName}</td>
<td>{employee.email}</td>
<td> {employee.departmentDto.departmentName}</td>
<td>
<button className="btn btn-info" onClick={() => updateEmployee(employee.id)} >Update</button>
<button className = "btn btn-danger" onClick = {() => removeEmployee(employee.id)}
style = {{marginLeft:"10px"}}> Delete</button>
</td>
</tr>
)
}
</tbody>
</table>
</div>
)
}
export default ListEmployeeComponent
Run React App
npm start
Comments
Post a Comment
Leave Comment