Azure SQL Database + Spring Boot + React - CRUD & Pagination Example

Hello everyone, Hope you are doing well. In this tutorial, you will learn how to build a Full stack CRUD, Pagination application with Spring BootSpring Data JPA, Microsoft Azure SQL Database, and React.

Technologies Used

Backend:

  • Java 17
  • Spring Boot 2.7.0
  • Spring Data JPA

Cloud Service:

  • Azure SQL Database

Frontend:

  • React 17.0.1
  • Axios 0.27.2
  • Bootstrap 4.6.0
  • React table 7.8.0

A little bit of Background

Azure SQL Database

Azure SQL Database is a fully managed platform as a service (PaaS) database engine that handles most of the database management functions such as upgrading, patching, backups, and monitoring without user involvement. Azure SQL Database is always running on the latest stable version of the SQL Server database engine and patched OS with 99.99% availability. PaaS capabilities built into Azure SQL Database enable you to focus on the domain-specific database administration and optimization activities that are critical for your business.

Spring Boot

Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can "just run". 


Spring Data JPA

Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA-based repositories. This module deals with enhanced support for JPA-based data access layers. It makes it easier to build Spring-powered applications that use data access technologies. 
Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer, you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.

React

React is a JavaScript library for building user interfaces.


Axios

Axios is the promise-based HTTP client for the browser.


Application Features:

  • Add User
  • Update User
  • Delete User
  • View User
  • Search users by country
  • Server-side pagination

Let's begin,

Sign in to Azure Portal and create a resource group

Sign in to Azure portal https://portal.azure.com/#home and find "Resource groups" like below.



Then, create a resource group like the one below.


Here the resource group name is "Knowledgefactory". Create a resource group by clicking the "Review + create " button.



Create an Azure SQL Database

Select "Azure SQL",

You will be taken to a page like the below image, Then click on the "Create Azure SQL resource" button.

Next, Select the SQL deployment option,
Select Resource type as Database server and then click on the "Create" button.

You will be taken to a page like the below image, 
Select/Enter necessary information like the above image and then, click on the "Review + create" button. Don't forget to keep the "Server admin login" name and "Password" somewhere safe.

Then click on the "Create" button.

Now, You can see "Deployment is in progress" like the below image.

Once deployment is completed you can see the "Your deployment is complete" page like the below image.

Then, click on the "Go to resource" button, and you will be taken to a page like the below image, 
Then, click on "Create database"

Create SQL Database,

Then, click on the "Review + create" button,

Then click on the "Create" button. You can see "Deployment is in progress"

Once deployment is completed you can see the "Your deployment is complete" page like the below image.

Click on the "Connection strings",
Copy it and keep it safe for future purposes.

Whitelisting IP Address for Access to Azure SQL DB, like the below image
Then click on the "Save" button


We will build two projects: 

1. Backend: springboot-azure-sql-crud-pagination
2. Frontend: react-datatable-pagination


Backend 

Backend Final Project Directory



pom.xml

A Project Object Model or POM is the fundamental unit of work in Maven. It is an XML file that contains information about the project and configuration details utilized by Maven to build the project. 
<?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.7.1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>springboot-azure-sql-crud-pagination</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-azure-sql-crud-pagination</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</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.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>



application.yaml

spring:
datasource:
username: <username>
password: <password>
url: jdbc:sqlserver://<servername>:1433;database=demo_db;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;
jpa:
show-sql: 'true'
hibernate:
ddl-auto: update
logging:
level:
org:
hibernate:
SQL: DEBUG
**Important note** Do not publish the fields directly for security. It would be a good choice to define these variables as environment variables or use the Azure Key Vault. 



User.java 

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.
package com.knf.dev.demo.springbootpagination.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.AUTO)
private Long id;
@Column(name = "name")
private String name;
@Column(name = "country")
private String country;
@Column(name = "email")
private String email;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
this.country = country;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public User(Long id, String name, String country, String email) {
super();
this.id = id;
this.name = name;
this.country = country;
this.email = email;
}

public User() {
super();
}

}



UserRepository.java

CrudRepository is a Spring Data interface for generic CRUD operations on a repository of a specific type. It provides several methods out of the box for interacting with a database.
package com.knf.dev.demo.springbootpagination.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import com.knf.dev.demo.springbootpagination.model.User;

public interface UserRepository
extends JpaRepository<User, Long> {

Page<User> findByCountryContaining
(String country, Pageable pageable);

}



ResourceNotFoundException.java

package com.knf.dev.demo.springbootpagination.exception;

public class ResourceNotFoundException extends RuntimeException {

private static final long serialVersionUID = 1L;

public ResourceNotFoundException(String message) {
super(message);
}
}



ServerError.java

package com.knf.dev.demo.springbootpagination.exception;

public class ServerError extends RuntimeException {

private static final long serialVersionUID = 1L;

public ServerError(String message) {
super(message);
}

}



GlobalExceptionHandler.java

Spring supports exception handling by a global Exception Handler (@ExceptionHandler) with Controller Advice (@ControllerAdvice). This enables a mechanism that makes ResponseEntity work with the type safety and flexibility of @ExceptionHandler:
package com.knf.dev.demo.springbootpagination.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> resourceNotFound
(Exception e, WebRequest request)

{
return new ResponseEntity<String>(e.getMessage(),
HttpStatus.NOT_FOUND);
}
}



UserController.java

The @RestController annotation was introduced in Spring 4.0 to simplify the engendering of RESTful web services. It's a convenience annotation that combines @Controller and @ResponseBody. @RequestMapping annotation maps HTTP requests to handler methods of MVC and REST controllers.
package com.knf.dev.demo.springbootpagination.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.knf.dev.demo.springbootpagination.exception.ResourceNotFoundException;
import com.knf.dev.demo.springbootpagination.exception.ServerError;
import com.knf.dev.demo.springbootpagination.model.User;
import com.knf.dev.demo.springbootpagination.repository.UserRepository;

@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api/v1/")
public class UserController {

@Autowired
UserRepository userRepsoitory;

@GetMapping("/users")
public Map<String, Object> getAllUsers(
@RequestParam(value = "country", required = false)
String country,
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "4") int size) {
try {

List<User> users = new ArrayList<User>();
Pageable pagination = PageRequest.of(page, size);
Page<User> userPage;
if (country == null) {
userPage = userRepsoitory.findAll(pagination);
} else {
userPage = userRepsoitory
.findByCountryContaining(country, pagination);
}
users = userPage.getContent();
Map<String, Object> response = new HashMap<String, Object>();
response.put("users", users);
response.put("totalPages", userPage.getTotalPages());
return response;
} catch (Exception e) {
throw new ServerError(e.getMessage());
}

}

@PostMapping("/users")
public User addUser(@RequestBody User user) {

return userRepsoitory.save(user);
}

@PutMapping("/users/{id}")
public User updateUser(@PathVariable("id") Long id,
@RequestBody User user) {

User userDetails = userRepsoitory.findById(id)
.orElseThrow(() -> new
ResourceNotFoundException("User Not Found"));
userDetails.setCountry(user.getCountry());
userDetails.setEmail(user.getEmail());
userDetails.setName(user.getName());

return userRepsoitory.save(userDetails);
}

@DeleteMapping("users/{id}")
public Boolean deleteUser(@PathVariable("id") Long id) {
User user = userRepsoitory.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User Not Found"));
userRepsoitory.delete(user);
return true;
}

@GetMapping("users/{id}")
public User findById(@PathVariable("id") Long id) {
User user = userRepsoitory.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User Not Found"));
return user;
}
}



Spring Boot Main Driver

package com.knf.dev.demo.springbootpagination;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootPaginationApplication {

public static void main(String[] args) {
SpringApplication.run(SpringbootPaginationApplication.class, args);
}

}




Frontend

Frontend Project Directory:



package.json

{
"name": "react-datatable-pagination",
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.11.3",
"@material-ui/lab": "^4.0.0-alpha.57",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@fortawesome/fontawesome-free": "^5.15.3",
"axios": "^0.27.2",
"bootstrap": "^4.6.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "5.0.1",
"react-table": "^7.8.0",
"web-vitals": "^1.0.1"
},
"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"
]
}
}



/services/UserService.js

import http from "../http-common";

const get = (id) => {
return http.get(`/users/${id}`);
};

const create = (data) => {
return http.post("/users", data);
};

const update = (id, data) => {
return http.put(`/users/${id}`, data);
};

const remove = (id) => {
return http.delete(`/users/${id}`);
};

const getAll = (params) => {
return http.get("/users", { params });
};

const UserService = {
getAll,
get,
create,
update,
remove
};

export default UserService;



/components/User.js

import React, { useState, useEffect } from "react";
import UserDataService from "../services/UserService";

const User = props => {
const initialUserState = {
id: null,
country: "",
email: "",
name: ""
};
const [currentUser, setCurrentUser] = useState(initialUserState);
const [message, setMessage] = useState("");

const getUser = id => {
UserDataService.get(id)
.then(response => {
setCurrentUser(response.data);
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};

useEffect(() => {
getUser(props.match.params.id);
}, [props.match.params.id]);

const handleInputChange = event => {
const { name, value } = event.target;
setCurrentUser({ ...currentUser, [name]: value });
};


const updateUser = () => {
UserDataService.update(currentUser.id, currentUser)
.then(response => {
console.log(response.data);
setMessage("The User was updated successfully!");
})
.catch(e => {
console.log(e);
});
};

const deleteUser = () => {
UserDataService.remove(currentUser.id)
.then(response => {
console.log(response.data);
props.history.push("/Users");
})
.catch(e => {
console.log(e);
});
};

return (
<div>
{currentUser ? (
<div className="edit-form">
<h4>User</h4>
<form>
<div className="form-group">
<label htmlFor="title">Name</label>
<input
type="text"
className="form-control"
id="name"
name="name"
value={currentUser.name}
onChange={handleInputChange}
/>
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<input
type="text"
className="form-control"
id="email"
name="email"
value={currentUser.email}
onChange={handleInputChange}
/>
</div>
<div className="form-group">
<label htmlFor="country">Country</label>
<input
type="text"
className="form-control"
id="country"
name="country"
value={currentUser.country}
onChange={handleInputChange}
/>
</div>
</form>

<button type="button" onClick={deleteUser}
class="btn btn-danger btn-sm">Delete</button>&nbsp;
<button type="button" onClick={updateUser}
class="btn btn-success btn-sm">Update</button>
<strong><p class="text-success">{message}</p></strong>
</div>
) : (
<div>
<br />
<p>Click on a User...</p>
</div>
)}
</div>
);
};

export default User;



/components/AddUser.js

import React, { useState } from "react";
import UserDataService from "../services/UserService";

const AddUser = () => {
const initialUserState = {
id: null,
country: "",
email: "",
name: ""
};
const [User, setUser] = useState(initialUserState);
const [submitted, setSubmitted] = useState(false);

const handleInputChange = event => {
const { name, value } = event.target;
setUser({ ...User, [name]: value });
};

const saveUser = () => {
var data = {
name: User.name,
email: User.email,
country: User.country
};

UserDataService.create(data)
.then(response => {
setUser({
id: response.data.id,
name: response.data.name,
email: response.data.email,
country: response.data.country
});
setSubmitted(true);
console.log(response.data);
})
.catch(e => {
console.log(e);
});
};

const newUser = () => {
setUser(initialUserState);
setSubmitted(false);
};

return (
<div className="edit-form">
{submitted ? (
<div>
<strong><p class="text-success">
Registration Successsful!</p></strong>
<button className="btn btn-success" onClick={newUser}>
Add
</button>
</div>
) : (
<div>
<div className="form-group">
<label htmlFor="name">Name</label>
<input
type="name"
className="form-control"
id="name"
required
value={User.name}
onChange={handleInputChange}
name="name"
/>
</div>

<div className="form-group">
<label htmlFor="email">Email</label>
<input
type="text"
className="form-control"
id="email"
required
value={User.email}
onChange={handleInputChange}
name="email"
/>
</div>

<div className="form-group">
<label htmlFor="country">Country</label>
<input
type="text"
className="form-control"
id="country"
required
value={User.country}
onChange={handleInputChange}
name="country"
/>
</div>

<button onClick={saveUser} className="btn btn-success">
Submit
</button>
</div>
)}
</div>
);
};

export default AddUser;



/components/UsersList.js

import React, { useState, useEffect, useMemo, useRef } from "react";
import Pagination from "@material-ui/lab/Pagination";
import UserDataService from "../services/UserService";
import { useTable } from "react-table";

const UsersList = (props) => {
const [users, setUsers] = useState([]);
const [searchCountry, setSearchCountry] = useState("");
const usersRef = useRef();

const [page, setPage] = useState(1);
const [count, setCount] = useState(0);
const [pageSize, setPageSize] = useState(4);

const pageSizes = [4, 8, 12];

usersRef.current = users;

const onChangeSearchCountry = (e) => {
const searchCountry = e.target.value;
setSearchCountry(searchCountry);
};

const getRequestParams = (searchCountry, page, pageSize) => {
let params = {};

if (searchCountry) {
params["country"] = searchCountry;
}

if (page) {
params["page"] = page - 1;
}

if (pageSize) {
params["size"] = pageSize;
}

return params;
};

const retrieveUsers = () => {
const params = getRequestParams(searchCountry, page, pageSize);

UserDataService.getAll(params)
.then((response) => {
const { users, totalPages } = response.data;

setUsers(users);
setCount(totalPages);

console.log(response.data);
})
.catch((e) => {
console.log(e);
});
};

useEffect(retrieveUsers, [page, pageSize]);

const findByCountry = () => {
setPage(1);
retrieveUsers();
};

const openUser = (rowIndex) => {
const id = usersRef.current[rowIndex].id;

props.history.push("/users/" + id);
};

const deleteUser = (rowIndex) => {
const id = usersRef.current[rowIndex].id;

UserDataService.remove(id)
.then((response) => {
props.history.push("/users");

let newUsers = [...usersRef.current];
newUsers.splice(rowIndex, 1);

setUsers(newUsers);
})
.catch((e) => {
console.log(e);
});
};

const handlePageChange = (event, value) => {
setPage(value);
};

const handlePageSizeChange = (event) => {
setPageSize(event.target.value);
setPage(1);
};

const columns = useMemo(
() => [
{
Header: "Name",
accessor: "name",
},
{
Header: "Email",
accessor: "email",
},
{
Header: "Country",
accessor: "country",
},
{
Header: "Actions",
accessor: "actions",
Cell: (props) => {
const rowIdx = props.row.id;
return (
<div>
<span onClick={() => openUser(rowIdx)}>
<button type="button" class="btn btn-warning btn-sm">
Edit</button>
</span>
&nbsp;
<span onClick={() => deleteUser(rowIdx)}>
<button type="button" class="btn btn-danger btn-sm">
Delete</button>
</span>
</div>
);
},
},
],
[]
);

const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data: users,
});

return (
<div className="list row">
<div className="col-md-8">
<div className="input-group mb-3">
<input
type="text"
className="form-control"
placeholder="Search by country"
value={searchCountry}
onChange={onChangeSearchCountry}
/>
<div className="input-group-append">
<button
className="btn btn-outline-success"
type="button"
onClick={findByCountry}
>
Search
</button>
</div>
</div>
</div>

<div className="col-md-12 list">
<div className="mt-3">
{"Items per Page: "}
<select onChange={handlePageSizeChange} value={pageSize}>
{pageSizes.map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</select>

<Pagination
color="primary"
className="my-3"
count={count}
page={page}
siblingCount={1}
boundaryCount={1}
variant="outlined"
onChange={handlePageChange}
/>
</div>

<table
className="table table-striped table-bordered"
{...getTableProps()}
>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>
{column.render("Header")}
</th>

))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</table>

<div className="mt-3">
{"Items per Page: "}
<select onChange={handlePageSizeChange} value={pageSize}>
{pageSizes.map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</select>

<Pagination
color="primary"
className="my-3"
count={count}
page={page}
siblingCount={1}
boundaryCount={1}
variant="outlined"
onChange={handlePageChange}
/>
</div>
</div>
</div>
);
};

export default UsersList;



http-common.js

import axios from "axios";

export default axios.create({
baseURL: "http://localhost:8080/api/v1",
headers: {
"Content-type": "application/json"
}
});



App.js

import React from "react";
import { Switch, Route, Link } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";
import "@fortawesome/fontawesome-free/css/all.css";
import "@fortawesome/fontawesome-free/js/all.js";

import AddUser from "./components/AddUser";
import User from "./components/User";
import UsersList from "./components/UsersList";

function App() {
return (
<div>
<nav className="navbar navbar-expand navbar-light bg-light">
<a href="/users" className="navbar-brand">
KnowledgeFactory
</a>
<div className="navbar-nav mr-auto">
<li className="nav-item">
<Link to={"/users"} className="nav-link">
Users
</Link>
</li>
<li className="nav-item">
<Link to={"/add"} className="nav-link">
Add User
</Link>
</li>
</div>
</nav>

<div className="container mt-3">
<Switch>
<Route exact path={["/", "/users"]} component={UsersList} />
<Route exact path="/add" component={AddUser} />
<Route path="/users/:id" component={User} />
</Switch>
</div>
</div>
);
}

export default App;



App.css

.list .action {
cursor: pointer;
}

.submit-form {
max-width: 4ex;
margin: auto;
}

.edit-form {
max-width: 400px;
margin: auto;
}



index.js

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);

reportWebVitals();



index.css

body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}



public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

Download the complete source code - click here

                                        

Local Setup and Run the application

Step1: Download or clone the source code from GitHub to the local machine - Click here


Backend

Step 2: mvn clean install

Step 3: Run the Spring Boot application - mvn spring-boot:run


Frontend

Step 4: npm install

Step 5: npm start

From the browser call the endpoint http://localhost:3000/

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete