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 build
command
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.
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 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.
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.jsxAdd 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.jsximport 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.jsimport 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.jsximport { 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.
Add User
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.
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.