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 Boot, Spring 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
- 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
- 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". More Info - https://spring.io/projects/spring-boot
Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can "just run".
More Info - https://spring.io/projects/spring-boot
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.
More Info - https://spring.io/projects/spring-data-jpa
React
React is a JavaScript library for building user interfaces.More Info - https://reactjs.org/docs/getting-started.html
React is a JavaScript library for building user interfaces.
More Info - https://reactjs.org/docs/getting-started.html
Axios
Axios is the promise-based HTTP client for the browser.More Info - https://axios-http.com/docs/intro
Axios is the promise-based HTTP client for the browser.
More Info - https://axios-http.com/docs/intro
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.
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.
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.
Then, click on the "Go to resource" button, and you will be taken to a page like the below image,
Create SQL Database,
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.
Whitelisting IP Address for Access to Azure SQL DB, like the below image
We will build two projects:
1. Backend: springboot-azure-sql-crud-pagination2. Frontend: react-datatable-pagination
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"
]
}
}
{
"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;
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>
<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;
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>
<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;
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>
<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;
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>
<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"
}
});
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;
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;
}
.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();
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;
}
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>
<!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
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