Spring Boot React JS CRUD Example

Spring Boot React JS CRUD Example thumbnail
99K
By Dhiraj 20 July, 2019

This tutorial is about creating a full-stack app using Spring Boot and React.js with example. With spring boot, we will build our backend app to expose REST endpoints to perform CRUD operations on a USER entity. For the persistence storage of user, we will be using MySQL DB. At the fronted side, we will be using React.js to create our user interface. The fronted app will be a single page application with Routing integrated with it. For the REST API calls, we will be using axios.

This article will be mostly React Js centric as we will discuss creating React App from scratch with all the basic concepts starting from the environment setup to creating a full-fledged CRUD app. Actually, you can refer this article to build the spring boot app from scratch with JWT authentication. This article can be equally used with the React app that we are going to build now.

React Js Environment Setup

We will be setting up our React app with CLI tool. For that, you need to have Node installed on your local machine. You can download the latest version from here. Once, you have Node installed we can start with generating our React app from CLI.

We will use CLI tool called create-react-app for creating react app which creates a sample app using react that can be used as a starting point to create a full-fledged react app. It has a dev server bundled by default for development. For a production build, u can run npm run buildcommand

We can directly use npm to install create-react-app and to generate react boilerplate app. But we will be using npx. With npx, we can generate a react app with creat-react-app but without installing create-react-app on our local machine.

npx is a package runner tool that comes with npm 5.2+. You’ll need to have Node >= 8.10 and npm >= 5.6 on your machine.

Traverse to the folder location where you want to generate the project and execute below commands:

npx create-react-app my-app
cd my-app
npm start

This will install react, react-dom, react-scripts and all the third parties libraries that we need to get started with React app. It will install a lightweight development server, webpack for bundling the files and Babel for compiling our JS code. In my case, it is it's React Js version of 16.8.6, react-router-dom version of 5.0.1 and axios version of 0.19.0.

Once done, the react app is deployed to our local dev server and available at localhost:3000

Above is the recommended way but we can npm also to generate the React app.

npm i -g create-react-app && create-react-app my-app
cd my-app
npm start

React Js Project Structure

Now, once the project is generated, let us import it into our workspace and loo into the project structure and different files generated.

reactjs-project-strct

package.json - Contains all the required dependencies for our React JS project. Most importantly, u 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 - 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.You can see below line which will actually render the App component(app.js) in the div element whose id is root and as discussed above this element is defined in index.html. Remember, Index.css is the global CSS.

App.js - Contains the definition our App component which actually gets rendered in the browser and this is the root component. It returns JSX which is a syntax extension to JavaScript. Actually, I find it very similar to old .jsp files without a $ sign. In JSX, to define an HTML class attribute, we actually use className instead of class. Remember, it follows camel-casing.

npm run eject - It ejects and copies all the configurations and scripts in our project to our local system in the folder config and script directory so that you can customize the default configurations.

Spring Boot Project Setup

Head over to start.spring.io to generate you spring boot project. We require spring data, MySQL and spring web for this project. Below is the final spring boot project structure that we will be building.

spring-boot-project-strct

Spring Boot Maven Dependency

Below is our pom.xml that contains all our spring boot dependencies.

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.1.RELEASE</version>
</parent>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
</dependencies>

React Components

Components are the building blocks of our whole react app. They are like functions which accepts inputs in terms of props, state and outputs a UI which is rendered in the browser. They are reusable and composable.

React components can be either a function component or a class component. We generally prefer function component over class component as they are simpler then class component but class components are more powerful then function component.

Function Component

 const UserComponent = (props) => {
	return  (
		
	);
 }

Class Component

 class UserComponent extends React.Component {
	render() {
		return (
			
		);
 }
 }

Inside the return function, we do not write actual html. We actually write JSX which is syntax extension to javascript. It gets compiled to pure Javascript calls by the React API.

Below is an example to understand the conversion.

<div className = "container">
	<h1>My First React App</h1>
</div>
React.createElement("div", {className: "container"},
	React.createElement("h1", null, "My First React App")
)

props are the list of object that an HTML element can have. Props are read-only and can not be modified.

Within a component, the state object can be changed but the props object represents a fixed value. props object are immutable and a component can only change their internal state but not their properties. The state can only be modified using this.setState().

If we want to change the prop value inside a child component that is passed from parent to a child component then we would need to go the parent component, modify the props and pass down the new value. This is a very cumbersome step and hence we have Redux. We will discuss REDUX in next article with example.

List User Component

Below is the List component that renders the user list. componentDidMount() is executed when the component is mounted for the first time. In the implementation, it actually invokes the service class method to fetch the users from an API call and populates the state variable users. When there is a change in the state, React Js reacts and updates the UI.

The implementation is purely in javascript . We are using the map operator to loop over our user list and create the view.

The cnstructor() is invoked before the component is mounted. In the constructor, we have declared our state variables and bind the different methods so that they are accessable from the state inside of the render() method.

You can not modify the state directly from the render() method and hence we have used this.setState({users: res.data.result}) rather then this.state.users = res.data.result. In React, the state changes are assynchronous.

ListUserComponent.jsx
import React, { Component } from 'react'
import ApiService from "../../service/ApiService";

class ListUserComponent extends Component {

    constructor(props) {
        super(props)
        this.state = {
            users: [],
            message: null
        }
        this.deleteUser = this.deleteUser.bind(this);
        this.editUser = this.editUser.bind(this);
        this.addUser = this.addUser.bind(this);
        this.reloadUserList = this.reloadUserList.bind(this);
    }

    componentDidMount() {
        this.reloadUserList();
    }

    reloadUserList() {
        ApiService.fetchUsers()
            .then((res) => {
                this.setState({users: res.data.result})
            });
    }

    deleteUser(userId) {
        ApiService.deleteUser(userId)
           .then(res => {
               this.setState({message : 'User deleted successfully.'});
               this.setState({users: this.state.users.filter(user => user.id !== userId)});
           })

    }

    editUser(id) {
        window.localStorage.setItem("userId", id);
        this.props.history.push('/edit-user');
    }

    addUser() {
        window.localStorage.removeItem("userId");
        this.props.history.push('/add-user');
    }

    render() {
        return (
            <div>
                <h2 className="text-center">User Details</h2>
                <button className="btn btn-danger" onClick={() => this.addUser()}> Add User</button>
                <table className="table table-striped">
                    <thead>
                        <tr>
                            <th className="hidden">Id</th>
                            <th>FirstName</th>
                            <th>LastName</th>
                            <th>UserName</th>
                            <th>Age</th>
                            <th>Salary</th>
                        </tr>
                    </thead>
                    <tbody>
                        {
                            this.state.users.map(
                        user =>
                                    <tr key={user.id}>
                                        <td>{user.firstName}</td>
                                        <td>{user.lastName}</td>
                                        <td>{user.username}</td>
                                        <td>{user.age}</td>
                                        <td>{user.salary}</td>
                                        <td>
                                            <button className="btn btn-success" onClick={() => this.deleteUser(user.id)}> Delete</button>
                                            <button className="btn btn-success" onClick={() => this.editUser(user.id)}> Edit</button>
                                        </td>
                                    </tr>
                            )
                        }
                    </tbody>
                </table>

            </div>
        );
    }

}

export default ListUserComponent;

Delete User

On the click of delete button, we directly make the API call to delete the user from DB and use filter operator to filter out the deleted user.

Edit User

On click of Edit button, we actually temporarily save selected user id in the browser localstorage and route to edit user component. This stored id will be used later.

Add User Component

AddUserComponent.jsx

Add user has a simple form. Each input type has onChange() method that actuallt sets the value in our component state. On click of save button, the API is called to save the user in the MySQL DB.

import React, { Component } from 'react'
import ApiService from "../../service/ApiService";

class AddUserComponent extends Component{

    constructor(props){
        super(props);
        this.state ={
            username: '',
            password: '',
            firstName: '',
            lastName: '',
            age: '',
            salary: '',
            message: null
        }
        this.saveUser = this.saveUser.bind(this);
    }

    saveUser = (e) => {
        e.preventDefault();
        let user = {username: this.state.username, password: this.state.password, firstName: this.state.firstName, lastName: this.state.lastName, age: this.state.age, salary: this.state.salary};
        ApiService.addUser(user)
            .then(res => {
                this.setState({message : 'User added successfully.'});
                this.props.history.push('/users');
            });
    }

    onChange = (e) =>
        this.setState({ [e.target.name]: e.target.value });

    render() {
        return(
            <div>
                <h2 className="text-center">Add User</h2>
                <form>
                <div className="form-group">
                    <label>User Name:</label>
                    <input type="text" placeholder="username" name="username" className="form-control" value={this.state.username} onChange={this.onChange}/>
                </div>

                <div className="form-group">
                    <label>Password:</label>
                    <input type="password" placeholder="password" name="password" className="form-control" value={this.state.password} onChange={this.onChange}/>
                </div>

                <div className="form-group">
                    <label>First Name:</label>
                    <input placeholder="First Name" name="firstName" className="form-control" value={this.state.firstName} onChange={this.onChange}/>
                </div>

                <div className="form-group">
                    <label>Last Name:</label>
                    <input placeholder="Last name" name="lastName" className="form-control" value={this.state.lastName} onChange={this.onChange}/>
                </div>

                <div className="form-group">
                    <label>Age:</label>
                    <input type="number" placeholder="age" name="age" className="form-control" value={this.state.age} onChange={this.onChange}/>
                </div>

                <div className="form-group">
                    <label>Salary:</label>
                    <input type="number" placeholder="salary" name="salary" className="form-control" value={this.state.salary} onChange={this.onChange}/>
                </div>

                <button className="btn btn-success" onClick={this.saveUser}>Save</button>
            </form>
    </div>
        );
    }
}

export default AddUserComponent;

Edit User Component

Edit component has a very similar implementation to Add user. There is an extra DB API call at the component mount to fetch the user by it's id to autopopulate the edit form. This is the same id that we saved in our Localstorage in the list user component.

EditUserComponent.jsx
import React, { Component } from 'react'
import ApiService from "../../service/ApiService";

class EditUserComponent extends Component {

    constructor(props){
        super(props);
        this.state ={
            id: '',
            firstName: '',
            lastName: '',
            age: '',
            salary: '',
        }
        this.saveUser = this.saveUser.bind(this);
        this.loadUser = this.loadUser.bind(this);
    }

    componentDidMount() {
        this.loadUser();
    }

    loadUser() {
        ApiService.fetchUserById(window.localStorage.getItem("userId"))
            .then((res) => {
                let user = res.data.result;
                this.setState({
                id: user.id,
                username: user.username,
                firstName: user.firstName,
                lastName: user.lastName,
                age: user.age,
                salary: user.salary,
                })
            });
    }

    onChange = (e) =>
        this.setState({ [e.target.name]: e.target.value });

    saveUser = (e) => {
        e.preventDefault();
        let user = {id: this.state.id, password: this.state.password, firstName: this.state.firstName, lastName: this.state.lastName, age: this.state.age, salary: this.state.salary};
        ApiService.editUser(user)
            .then(res => {
                this.setState({message : 'User added successfully.'});
                this.props.history.push('/users');
            });
    }

    render() {
        return (
            <div>
                <h2 className="text-center">Edit User</h2>
                <form>

                    <div className="form-group">
                        <label>User Name:</label>
                        <input type="text" placeholder="username" name="username" className="form-control" readonly="true" defaultValue={this.state.username}/>
                    </div>

                    <div className="form-group">
                        <label>First Name:</label>
                        <input placeholder="First Name" name="firstName" className="form-control" value={this.state.firstName} onChange={this.onChange}/>
                    </div>

                    <div className="form-group">
                        <label>Last Name:</label>
                        <input placeholder="Last name" name="lastName" className="form-control" value={this.state.lastName} onChange={this.onChange}/>
                    </div>

                    <div className="form-group">
                        <label>Age:</label>
                        <input type="number" placeholder="age" name="age" className="form-control" value={this.state.age} onChange={this.onChange}/>
                    </div>

                    <div className="form-group">
                        <label>Salary:</label>
                        <input type="number" placeholder="salary" name="salary" className="form-control" value={this.state.salary} onChange={this.onChange}/>
                    </div>

                    <button className="btn btn-success" onClick={this.saveUser}>Save</button>
                </form>
            </div>
        );
    }
}

export default EditUserComponent;

Routing Component

Let us integrate routing in our React app. For this, we need a 3rd party library called as react-router-domand to do so let us use the below CLI command.

npm add react-router-dom

Once this is done, we can actually define our routes. We have 3 routes defined each for list user, add user and edit user. We have our route configuration in app.js

App.js
import React from 'react';
import './App.css';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import ListUserComponent from "./component/user/ListUserComponent";
import AddUserComponent from "./component/user/AddUserComponent";
import EditUserComponent from "./component/user/EditUserComponent";

function App() {
  return (
      <div className="container">
          <Router>
              <div className="col-md-6">
                  <h1 className="text-center" style={style}>React User Application</h1>
                  <Switch>
                      <Route path="/" exact component={ListUserComponent} />
                      <Route path="/users" component={ListUserComponent} />
                      <Route path="/add-user" component={AddUserComponent} />
                      <Route path="/edit-user" component={EditUserComponent} />
                  </Switch>
              </div>
          </Router>
      </div>
  );
}

const style = {
    color: 'red',
    margin: '10px'
}

export default App;

As our application is a very simple app, we have configured the routing in App.js itself rather then creating a different component for routing and including that component in App.js.

In case you want to create a different component for routing and add it in App.js then you can do so as below:

RouterComponent.jsx
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import ListUserComponent from "./user/ListUserComponent";
import AddUserComponent from "./user/AddUserComponent";
import EditUserComponent from "./user/EditUserComponent";
import React from "react";

const AppRouter = () => {
    return(
        <div>
            <Router>
                <div className="col-md-6">
                    <h1 className="text-center" style={style}>React User Application</h1>
                    <Switch>
                        <Route path="/" exact component={ListUserComponent} />
                        <Route path="/users" component={ListUserComponent} />
                        <Route path="/add-user" component={AddUserComponent} />
                        <Route path="/edit-user" component={EditUserComponent} />
                    </Switch>
                </div>
            </Router>
        </div>
    )
}

const style = {
    color: 'red',
    margin: '10px'
}

export default AppRouter;
App.js
import React from 'react';
import './App.css';
import AppRouter from "./component/RouterComponent";

function App() {
  return (
      <div className="container">
          <AppRouter/>
      </div>
  );
}

export default App;

React Service Component

For our API calls, we will be using axios. Below is the npm command to install axios.

npm add axios

Below is the React js service implementation to make our HTTP REST call via axios. Our backend USER endpoint is avilable at http://localhost:8080/users

import axios from 'axios';

const USER_API_BASE_URL = 'http://localhost:8080/users';

class ApiService {

    fetchUsers() {
        return axios.get(USER_API_BASE_URL);
    }

    fetchUserById(userId) {
        return axios.get(USER_API_BASE_URL + '/' + userId);
    }

    deleteUser(userId) {
        return axios.delete(USER_API_BASE_URL + '/' + userId);
    }

    addUser(user) {
        return axios.post(""+USER_API_BASE_URL, user);
    }

    editUser(user) {
        return axios.put(USER_API_BASE_URL + '/' + user.id, user);
    }

}

export default new ApiService();

Spring Boot REST

Let us defne our controller that has all the endpoints for the CRUD operation.

UserController.java
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public ApiResponse<User> saveUser(@RequestBody UserDto user){
        return new ApiResponse<>(HttpStatus.OK.value(), "User saved successfully.",userService.save(user));
    }

    @GetMapping
    public ApiResponse<List<User>> listUser(){
        return new ApiResponse<>(HttpStatus.OK.value(), "User list fetched successfully.",userService.findAll());
    }

    @GetMapping("/{id}")
    public ApiResponse<User> getOne(@PathVariable int id){
        return new ApiResponse<>(HttpStatus.OK.value(), "User fetched successfully.",userService.findById(id));
    }

    @PutMapping("/{id}")
    public ApiResponse<UserDto> update(@RequestBody UserDto userDto) {
        return new ApiResponse<>(HttpStatus.OK.value(), "User updated successfully.",userService.update(userDto));
    }

    @DeleteMapping("/{id}")
    public ApiResponse<Void> delete(@PathVariable int id) {
        userService.delete(id);
        return new ApiResponse<>(HttpStatus.OK.value(), "User deleted successfully.", null);
    }

}

Below are the API details.

List User

API Name  - List User
URL - http://localhost:8080/users
Method - Get
Header - Content-Type: application/json
    
Response -
{
    "status": 200,
    "message": "User list fetched successfully.",
    "result": [
        {
            "id": 1,
            "firstName": "Alex",
            "lastName": "Knr",
            "username": "alex123",
            "salary": 3456,
            "age": 33
        },
        {
            "id": 2,
            "firstName": "Tom",
            "lastName": "Asr",
            "username": "tom234",
            "salary": 7823,
            "age": 23
        },
        {
            "id": 3,
            "firstName": "Adam",
            "lastName": "Psr",
            "username": "adam",
            "salary": 4234,
            "age": 45
        }
    ]
}

Add User

API Name  - Create User
URL - http://localhost:8080/users
Method - POST
Header - Content-Type: application/json
   
Body -
{
	"username":"test",
	"password":"test",
	"firstName":"test",
	"lastName":"test",
	"age":23,
	"salary":12345
}

Response -

{
    "status": 200,
    "message": "User saved successfully.",
    "result": {
        "id": 4,
        "firstName": "test",
        "lastName": "test"",
        "username": "test",
        "salary": 12345,
        "age": 23
    }
}


Update user

API Name  - Update User
URL - http://localhost:8080/users/4
Method - PUT
Header - Content-Type: application/json

Body -
{
	"username":"test1",
	"password":"test1",
	"firstName":"test1",
	"lastName":"test1",
	"age":24,
	"salary":12345
}

Response -

{
    "status": 200,
    "message": "User updated successfully.",
    "result": {
        "id": 0,
        "firstName": "test1",
        "lastName": "test1",
        "username": "test1",
        "password": "test1",
        "age": 24,
        "salary": 12345
    }
}


Delete User

API Name  - Delete User
URL - http://localhost:8080/users/4
Method - DELETE
Header - Content-Type: application/json
  
Response -
{
    "status": 200,
    "message": "User deleted successfully.",
    "result": null
}

MySQL DB Configuration

Below is our application.properties file that has the DB configs.

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.user.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

Testing Spring Boot React Js App

First let us start our Spring Boot app. Run Application.java as a main class and once deployed you can hit http://localhost:8080/users in the browser to get the JSON response of user list.

User is added in the DB from the Spring Boot CommandlineRunner at application startup.

Now, let us run our React JS app. To do so, execute below command in the CLI.

npm start

List User

Hit http://localhost:3000 to render the user list page.

spring-boot-reactjs-list-user

Add User

spring-boot-reactjs-add-user

Edit User

spring-boot-reactjs-edit-user

Adding Material UI to React App

If you will look into index.html we have added Twitter bootstrap library for our basic styling purpose. In fact, you can integrate React material UI with this app for a nice look and feel. The material UI has almost all the UI components pre-built such as Layout, Inputs, Navigation, etc. with React support and hence it is very easy to integrate and use with an existing React app.

To add Material UI in our existing React app, you need to run below CLI commands and start using the pre-built components directly.

npm install @material-ui/core
npm install @material-ui/icons

material-ui/core has all the core components related to Layout, Inputs, Navigation, etc. and with material-ui/icons, we can use all the prebuilt SVG Material icons.

I have another article where I have integrated material ui in this same app. You can follow that article here. Below is a screenshot of the list page.

react-js-material-list-user

Conclusion

In this tutorial, we created a sample app using React Js and Spring Boot and performed different CRUD operations. In the next article, we will integrate REDUX and material design in it.

Coming to deployment, either you can deploy it to a standalone server on cloud or else as a single project as discussed here.

You can find the React app here on github and the backend app here.

Share

If You Appreciate This, You Can Consider:

We are thankful for your never ending support.

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on React JS