SpringBoot + React JS + JPA - CRUD example
Hello everyone, today we will learn how to develop a full-stack web application that is a basic User Management Application using React, Spring Boot.GitHub repository link is provided at the end of this tutorial. You can download the source code.
Technologies used:
Back-End:
- Java 1.8
- Spring Boot 2+
- Maven 3.2+
- H2DB
Front-End:
- React
- react-router-dom 5+
- axios 0.19.2
- bootstrap 4.4.1
Spring Boot +React JS CRUD Full Stack Architecture
Project Structure of Back-End:
Project Structure of Front-End:
After completing this tutorial what we will build?
We will build a full-stack web application that is a basic User Management Application with CRUD features:
• Create User
• List User
• Update User
• Delete User
• View User
Following is the screenshot of our application -
A quick overview of React and Spring Boot
What and Why React.js?
React.js is an open-source JavaScript library that is used for building user interfaces specifically for single-page applications. React allows developers to create large web applications that can change data, without reloading the page. The main purpose of React is to be fast, scalable, and simple. It works only on user interfaces in the application.
What and Why Spring Boot?
Spring boot is used to develop REST web services and microservices. Spring Boot has taken the Spring framework to the next level. It has drastically reduced the configuration and setup time required for spring projects. We can set up a project with almost zero configuration and start building the things that actually matter to your application.
Full-Stack App Development
We divided this tutorial into two parts
PART 1 - Restful API Development with Spring Boot (back end)
PART 2 - UI development using Recat.js(front end/client)
Project 1:Spring Boot-CRUD-Restful-API
pom.xml
Include spring-boot-starter-web for Spring MVC and REST structure, spring-boot-starter-data-jpa for CRUD repository.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven
.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.sibin.dev</groupId>
<artifactId>springboot_crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<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.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Model(User)
package com.knf.sibin.dev.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "user")
public class User {
@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 emailId;
public User() {
}
public User(String firstName, String lastName, String emailId) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.emailId = emailId;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
}
The @Entity annotation specifies that the class is an entity and is mapped to a database table. The @Id annotation specifies the primary key of an entity and the @GeneratedValue provides for the specification of generation strategies for the values of primary keys. The @Column annotation is used to specify the mapped column for a persistent property or field. If no Column annotation is specified, the default value will be applied.
Repository(UserRepository)
package com.knf.sibin.dev.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.knf.sibin.dev.model.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
Rest Controller
package com.knf.sibin.dev.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.knf.sibin.dev.exception.ResourceNotFoundException;
import com.knf.sibin.dev.model.User;
import com.knf.sibin.dev.repository.UserRepository;
@CrossOrigin(origins = "http://localhost:3000")
@RestController
@RequestMapping("/api/v1/")
public class UserController {
@Autowired
private UserRepository userRepository;
// get all users
@GetMapping("/users")
public List<User> getAllUsers() {
return userRepository.findAll();
}
// create user rest API
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userRepository.save(user);
}
// get user by id rest api
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
return ResponseEntity.ok(user);
}
// update user rest api
@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id,
@RequestBody User userDetails) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
user.setFirstName(userDetails.getFirstName());
user.setLastName(userDetails.getLastName());
user.setEmailId(userDetails.getEmailId());
User updatedUser = userRepository.save(user);
return ResponseEntity.ok(updatedUser);
}
// delete user rest api
@DeleteMapping("/users/{id}")
public ResponseEntity<Map<String, Boolean>> deleteUser
(@PathVariable Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
userRepository.delete(user);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return ResponseEntity.ok(response);
}
}
Main Class
package com.knf.sibin.dev;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
Github repository download link is provided at the end of this tutorial
Github repository download link is provided at the end of this tutorial
Backend Local Setup:
Step 1: Download or clone the source code to a local machine.
Step 2: mvn clean install
Step 3: Run the Spring Boot application
mvn spring-boot:run
Project 2: React JS App - Front-End
package.json
It is used by the npm CLI (and yarn) to identify your project and understand how to handle the project's dependencies. It's the package. json file that enables npm to start your project, run scripts, install dependencies, publish to the NPM registry, and many other useful tasks.
{
"name": "react-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"axios": "^0.19.2",
"bootstrap": "^4.5.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
CreateUserComponent.json
import React, { Component } from 'react'
import UserService from '../services/UserService';
class CreateUserComponent extends Component {
constructor(props) {
super(props)
this.state = {
// step 2
id: this.props.match.params.id,
firstName: '',
lastName: '',
emailId: ''
}
this.changeFirstNameHandler = this.changeFirstNameHandler.bind(this);
this.changeLastNameHandler = this.changeLastNameHandler.bind(this);
this.saveOrUpdateUser = this.saveOrUpdateUser.bind(this);
}
// step 3
componentDidMount() {
// step 4
if (this.state.id === '_add') {
return
} else {
UserService.getUserById(this.state.id).then((res) => {
let user = res.data;
this.setState({
firstName: user.firstName,
lastName: user.lastName,
emailId: user.emailId
});
});
}
}
saveOrUpdateUser = (e) => {
e.preventDefault();
let user = { firstName: this.state.firstName, lastName:
this.state.lastName, emailId: this.state.emailId };
console.log('user => ' + JSON.stringify(user));
// step 5
if (this.state.id === '_add') {
UserService.createUser(user).then(res => {
this.props.history.push('/users');
});
} else {
UserService.updateUser(user, this.state.id).then(res => {
this.props.history.push('/users');
});
}
}
changeFirstNameHandler = (event) => {
this.setState({ firstName: event.target.value });
}
changeLastNameHandler = (event) => {
this.setState({ lastName: event.target.value });
}
changeEmailHandler = (event) => {
this.setState({ emailId: event.target.value });
}
cancel() {
this.props.history.push('/users');
}
getTitle() {
if (this.state.id === '_add') {
return <h3 className="text-center">Add User</h3>
} else {
return <h3 className="text-center">Update User</h3>
}
}
render() {
return (
<div>
<br></br>
<div className="container">
<div className="row">
<div className="card col-md-6 offset-md-3 offset-md-3">
{
this.getTitle()
}
<div className="card-body">
<form>
<div className="form-group">
<label> First Name: </label>
<input placeholder="First Name"
name="firstName" className="form-control"
value={this.state.firstName}
onChange={this.changeFirstNameHandler} />
</div>
<div className="form-group">
<label> Last Name: </label>
<input placeholder="Last Name"
name="lastName" className="form-control"
value={this.state.lastName}
onChange={this.changeLastNameHandler} />
</div>
<div className="form-group">
<label> Email Id: </label>
<input placeholder="Email Address"
name="emailId" className="form-control"
value={this.state.emailId}
onChange={this.changeEmailHandler} />
</div>
<button className="btn btn-success"
onClick={this.saveOrUpdateUser}>Save</button>
<button className="btn btn-danger"
onClick={this.cancel.bind(this)}
style={{ marginLeft: "10px" }}>Cancel</button>
</form>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default CreateUserComponent
ListUserComponent.jsx
import React, { Component } from 'react'
import UserService from '../services/UserService'
class ListUserComponent extends Component {
constructor(props) {
super(props)
this.state = {
users: []
}
this.addUser = this.addUser.bind(this);
this.editUser = this.editUser.bind(this);
this.deleteUser = this.deleteUser.bind(this);
}
deleteUser(id) {
UserService.deleteUser(id).then(res => {
this.setState({ users: this.state.users.
filter(user => user.id !== id) });
});
}
viewUser(id) {
this.props.history.push(`/view-user/${id}`);
}
editUser(id) {
this.props.history.push(`/add-user/${id}`);
}
componentDidMount() {
UserService.getUsers().then((res) => {
this.setState({ users: res.data });
});
}
addUser() {
this.props.history.push('/add-user/_add');
}
render() {
return (
<div>
<h2 className="text-center">Users List</h2>
<div className="row">
<button className="btn btn-primary"
onClick={this.addUser}> Add User</button>
</div>
<br></br>
<div className="row">
<table className="table table-striped table-bordered">
<thead>
<tr>
<th> User First Name</th>
<th> User Last Name</th>
<th> User Email Id</th>
<th> Actions</th>
</tr>
</thead>
<tbody>
{
this.state.users.map(
user =>
<tr key={user.id}>
<td> {user.firstName} </td>
<td> {user.lastName}</td>
<td> {user.emailId}</td>
<td>
<button onClick={() => this.editUser(user.id)} className="btn btn-info">
Update </button>
<button style={{ marginLeft: "10px" }} onClick={() => this.
deleteUser(user.id)} className="btn btn-danger">Delete </button>
<button style={{ marginLeft: "10px" }} onClick={() => this.
viewUser(user.id)} className="btn btn-info">View </button>
</td>
</tr>
)
}
</tbody>
</table>
</div>
</div>
)
}
}
export default ListUserComponent
UpdateUserComponent.jsx
import React, { Component } from 'react'
import UserService from '../services/UserService';
class UpdateUserComponent extends Component {
constructor(props) {
super(props)
this.state = {
id: this.props.match.params.id,
firstName: '',
lastName: '',
emailId: ''
}
this.changeFirstNameHandler = this.changeFirstNameHandler.bind(this);
this.changeLastNameHandler = this.changeLastNameHandler.bind(this);
this.updateUser = this.updateUser.bind(this);
}
componentDidMount() {
UserService.getUserById(this.state.id).then((res) => {
let user = res.data;
this.setState({
firstName: user.firstName,
lastName: user.lastName,
emailId: user.emailId
});
});
}
updateUser = (e) => {
e.preventDefault();
let user = { firstName: this.state.firstName,
lastName: this.state.lastName, emailId: this.state.emailId };
console.log('user => ' + JSON.stringify(user));
console.log('id => ' + JSON.stringify(this.state.id));
UserService.updateUser(user, this.state.id).then(res => {
this.props.history.push('/users');
});
}
changeFirstNameHandler = (event) => {
this.setState({ firstName: event.target.value });
}
changeLastNameHandler = (event) => {
this.setState({ lastName: event.target.value });
}
changeEmailHandler = (event) => {
this.setState({ emailId: event.target.value });
}
cancel() {
this.props.history.push('/users');
}
render() {
return (
<div>
<br></br>
<div className="container">
<div className="row">
<div className="card col-md-6 offset-md-3 offset-md-3">
<h3 className="text-center">Update User</h3>
<div className="card-body">
<form>
<div className="form-group">
<label> First Name: </label>
<input placeholder="First Name" name="firstName" className="form-control"
value={this.state.firstName} onChange={this.changeFirstNameHandler} />
</div>
<div className="form-group">
<label> Last Name: </label>
<input placeholder="Last Name" name="lastName" className="form-control"
value={this.state.lastName} onChange={this.changeLastNameHandler} />
</div>
<div className="form-group">
<label> Email Id: </label>
<input placeholder="Email Address" name="emailId" className="form-control"
value={this.state.emailId} onChange={this.changeEmailHandler} />
</div>
<button className="btn btn-success" onClick={this.updateUser}>Save</button>
<button className="btn btn-danger" onClick={this.cancel.bind(this)}
style={{ marginLeft: "10px" }}>Cancel</button>
</form>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default UpdateUserComponent
ViewUserComponent.jsx
import React, { Component } from 'react'
import UserService from '../services/UserService'
class ViewUserComponent extends Component {
constructor(props) {
super(props)
this.state = {
id: this.props.match.params.id,
user: {}
}
}
componentDidMount() {
UserService.getUserById(this.state.id).then(res => {
this.setState({ user: res.data });
})
}
render() {
return (
<div>
<br></br>
<div className="card col-md-6 offset-md-3">
<h3 className="text-center"> View User Details</h3>
<div className="card-body">
<div className="row">
<label> User First Name: </label>
<div> {this.state.user.firstName}</div>
</div>
<div className="row">
<label> User Last Name: </label>
<div> {this.state.user.lastName}</div>
</div>
<div className="row">
<label> User Email ID: </label>
<div> {this.state.user.emailId}</div>
</div>
</div>
</div>
</div>
)
}
}
export default ViewUserComponent
HeaderComponent.jsx
import React, { Component } from 'react'
import 'bootstrap/dist/css/bootstrap.min.css';
class HeaderComponent extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<div>
<header>
<nav className="navbar navbar-dark bg-primary">
<div>
<a href="knowledgefactory.net"
className="navbar-brand">
User Management App</a></div>
</nav>
</header>
</div>
)
}
}
export default HeaderComponent
UserService.js
import axios from 'axios';
const USER_API_BASE_URL = "http://localhost:8080/api/v1/users";
class UserService {
getUsers() {
return axios.get(USER_API_BASE_URL);
}
createUser(user) {
return axios.post(USER_API_BASE_URL, user);
}
getUserById(userId) {
return axios.get(USER_API_BASE_URL + '/' + userId);
}
updateUser(user, userId) {
return axios.put(USER_API_BASE_URL + '/' + userId, user);
}
deleteUser(userId) {
return axios.delete(USER_API_BASE_URL + '/' + userId);
}
}
export default new UserService()
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import 'bootstrap/dist/css/bootstrap.min.css';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
App.js:
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import ListUserComponent from './components/ListUserComponent';
import HeaderComponent from './components/HeaderComponent';
import FooterComponent from './components/FooterComponent';
import CreateUserComponent from './components/CreateUserComponent';
import UpdateUserComponent from './components/UpdateUserComponent';
import ViewUserComponent from './components/ViewUserComponent';
function App() {
return (
<div>
<Router>
<HeaderComponent />
<div className="container">
<Switch>
<Route path="/" exact component={ListUserComponent}></Route>
<Route path="/users" component={ListUserComponent}></Route>
<Route path="/add-user/:id" component={CreateUserComponent}></Route>
<Route path="/view-user/:id" component={ViewUserComponent}></Route>
</Switch>
</div>
<FooterComponent />
</Router>
</div>
);
}
export default App;
Frontend Local Setup:
Step 1: Download or clone the source code to a local machine.
Step 2: npm install
Step 3: Run the React JS application
npm start
Download the Backend source code:
Download the Frontend source code:
More related topics,
Comments
Post a Comment