1. Введение
В этом руководстве мы узнаем, как создать приложение, способное создавать, извлекать, обновлять и удалять (CRUD) клиентские данные. Приложение будет состоять из простого Spring Boot RESTful API и пользовательского интерфейса (UI), реализованного с помощью библиотеки React JavaScript .
2. Весенний ботинок
2.1. Зависимости Maven
Начнем с добавления нескольких зависимостей в наш файл pom.xml
:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>runtime</scope>
</dependency>
</dependencies>
Здесь мы добавили веб-стартеры, тестирование и персистентность JPA, а также зависимость H2, так как приложение будет иметь базу данных H2 в памяти.
2.2. Создание модели
Затем давайте создадим наш класс сущности Client со свойствами
имени
и электронной почты
, чтобы представить нашу модель данных:
@Entity
@Table(name = "client")
public class Client {
@Id
@GeneratedValue
private Long id;
private String name;
private String email;
// getter, setters, contructors
}
2.3. Создание репозитория
Затем мы создадим наш класс ClientRepository ,
расширяющий JpaRepository
, чтобы предоставить возможности JPA CRUD :
public interface ClientRepository extends JpaRepository<Client, Long> {
}
2.4. Создание REST-контроллера
Наконец, давайте представим REST API, создав контроллер для взаимодействия с ClientRepository
:
@RestController
@RequestMapping("/clients")
public class ClientsController {
private final ClientRepository clientRepository;
public ClientsController(ClientRepository clientRepository) {
this.clientRepository = clientRepository;
}
@GetMapping
public List<Client> getClients() {
return clientRepository.findAll();
}
@GetMapping("/{id}")
public Client getClient(@PathVariable Long id) {
return clientRepository.findById(id).orElseThrow(RuntimeException::new);
}
@PostMapping
public ResponseEntity createClient(@RequestBody Client client) throws URISyntaxException {
Client savedClient = clientRepository.save(client);
return ResponseEntity.created(new URI("/clients/" + savedClient.getId())).body(savedClient);
}
@PutMapping("/{id}")
public ResponseEntity updateClient(@PathVariable Long id, @RequestBody Client client) {
Client currentClient = clientRepository.findById(id).orElseThrow(RuntimeException::new);
currentClient.setName(client.getName());
currentClient.setEmail(client.getEmail());
currentClient = clientRepository.save(client);
return ResponseEntity.ok(currentClient);
}
@DeleteMapping("/{id}")
public ResponseEntity deleteClient(@PathVariable Long id) {
clientRepository.deleteById(id);
return ResponseEntity.ok().build();
}
}
2.5. Запуск нашего API
После этого мы готовы запустить наш Spring Boot API. Мы можем сделать это с помощью плагина spring-boot-maven
:
mvn spring-boot:run
Затем мы сможем получить список наших клиентов, перейдя по адресу http://localhost:8080/clients .
2.6. Создание клиентов
Кроме того, мы можем создать несколько клиентов с помощью Postman :
curl -X POST http://localhost:8080/clients -d '{"name": "John Doe", "email": "john.doe@baeldgung.com"}'
3. Реагировать
React — это библиотека JavaScript для создания пользовательских интерфейсов. Для работы с React требуется установленный Node.js. Мы можем найти инструкции по установке на странице загрузки Node.js.
3.1. Создание пользовательского интерфейса React
Create React App — это командная утилита, которая генерирует для нас проекты React . Давайте создадим наше внешнее
приложение в нашем базовом каталоге приложений Spring Boot, выполнив:
npx create-react-app frontend
После завершения процесса создания приложения мы установим Bootstrap , React Router и reactstrap в директорию внешнего интерфейса
:
npm install --save bootstrap@5.1 react-cookie@4.1.1 react-router-dom@5.3.0 reactstrap@8.10.0
Мы будем использовать CSS Bootstrap и компоненты reactstrap для создания более привлекательного пользовательского интерфейса, а также компоненты React Router для управления навигацией по приложению.
Давайте добавим CSS-файл Bootstrap в качестве импорта в app/src/index.js
:
import 'bootstrap/dist/css/bootstrap.min.css';
3.2. Запускаем наш React UI
Теперь мы готовы запустить наше внешнее
приложение:
npm start
При доступе к http://localhost:3000 в нашем браузере мы должны увидеть пример страницы React:
3.3. Вызов нашего Spring Boot API
Для вызова нашего API Spring Boot требуется настроить файл package.json
нашего приложения React для настройки прокси-сервера при вызове API.
Для этого мы включим URL-адрес нашего API в package.json
:
...
"proxy": "http://localhost:8080",
...
Далее давайте отредактируем frontend/src/App.js
, чтобы он вызывал наш API для отображения списка клиентов с именем
и свойствами электронной почты :
class App extends Component {
state = {
clients: []
};
async componentDidMount() {
const response = await fetch('/clients');
const body = await response.json();
this.setState({clients: body});
}
render() {
const {clients} = this.state;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<div className="App-intro">
<h2>Clients</h2>
{clients.map(client =>
<div key={client.id}>
{client.name} ({client.email})
</div>
)}
</div>
</header>
</div>
);
}
}
export default App;
В функции componentDidMount
мы получаем наш клиентский API и
устанавливаем тело ответа в переменной client. В нашей функции рендеринга
мы возвращаем HTML со списком клиентов, найденным в API.
Мы увидим страницу нашего клиента, которая будет выглядеть так:
Примечание. Убедитесь, что приложение Spring Boot запущено, чтобы пользовательский интерфейс мог вызывать API.
3.4. Создание компонента ClientList
Теперь мы можем улучшить наш пользовательский интерфейс, чтобы отобразить более сложный компонент для перечисления
, редактирования
, удаления
и создания клиентов
с помощью нашего API. Позже мы увидим, как использовать этот компонент и удалить
список клиентов из компонента App .
Создадим файл в frontend/src/ClientList.js
:
import React, { Component } from 'react';
import { Button, ButtonGroup, Container, Table } from 'reactstrap';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
class ClientList extends Component {
constructor(props) {
super(props);
this.state = {clients: []};
this.remove = this.remove.bind(this);
}
componentDidMount() {
fetch('/clients')
.then(response => response.json())
.then(data => this.setState({clients: data}));
}
}
export default ClientList;
Как и в App.js
, функция componentDidMount
вызывает наш API для загрузки нашего списка клиентов.
Мы также добавим функцию удаления для обработки вызова
DELETE
к API, когда мы хотим удалить клиента. Кроме того, мы создадим функцию рендеринга
, которая будет отображать HTML с помощью действий Edit
, Delete
и Add Client :
async remove(id) {
await fetch(`/clients/${id}`, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then(() => {
let updatedClients = [...this.state.clients].filter(i => i.id !== id);
this.setState({clients: updatedClients});
});
}
render() {
const {clients, isLoading} = this.state;
if (isLoading) {
return <p>Loading...</p>;
}
const clientList = clients.map(client => {
return <tr key={client.id}>
<td style={{whiteSpace: 'nowrap'}}>{client.name}</td>
<td>{client.email}</td>
<td>
<ButtonGroup>
<Button size="sm" color="primary" tag={Link} to={"/clients/" + client.id}>Edit</Button>
<Button size="sm" color="danger" onClick={() => this.remove(client.id)}>Delete</Button>
</ButtonGroup>
</td>
</tr>
});
return (
<div>
<AppNavbar/>
<Container fluid>
<div className="float-right">
<Button color="success" tag={Link} to="/clients/new">Add Client</Button>
</div>
<h3>Clients</h3>
<Table className="mt-4">
<thead>
<tr>
<th width="30%">Name</th>
<th width="30%">Email</th>
<th width="40%">Actions</th>
</tr>
</thead>
<tbody>
{clientList}
</tbody>
</Table>
</Container>
</div>
);
}
3.5. Создание компонента ClientEdit
Компонент ClientEdit
будет отвечать за создание и редактирование нашего клиента .
Создадим файл в frontend/src/ClientEdit.js
:
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
import { Button, Container, Form, FormGroup, Input, Label } from 'reactstrap';
import AppNavbar from './AppNavbar';
class ClientEdit extends Component {
emptyItem = {
name: '',
email: ''
};
constructor(props) {
super(props);
this.state = {
item: this.emptyItem
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
}
export default withRouter(ClientEdit);
Давайте добавим функцию componentDidMount
, чтобы проверить, имеем ли мы дело с функцией создания или редактирования; в случае редактирования он будет получать наш клиент из API:
async componentDidMount() {
if (this.props.match.params.id !== 'new') {
const client = await (await fetch(`/clients/${this.props.match.params.id}`)).json();
this.setState({item: client});
}
}
Затем в функции handleChange
мы обновим свойство элемента состояния нашего компонента, которое будет использоваться при отправке нашей формы:
handleChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
let item = {...this.state.item};
item[name] = value;
this.setState({item});
}
В handeSubmit
мы будем вызывать наш API, отправляя запрос методу PUT
или POST
в зависимости от функции, которую мы вызываем. Для этого мы можем проверить, заполнено ли свойство id :
async handleSubmit(event) {
event.preventDefault();
const {item} = this.state;
await fetch('/clients' + (item.id ? '/' + item.id : ''), {
method: (item.id) ? 'PUT' : 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item),
});
this.props.history.push('/clients');
}
И последнее, но не менее важное: наша функция рендеринга
будет обрабатывать нашу форму:
render() {
const {item} = this.state;
const title = <h2>{item.id ? 'Edit Client' : 'Add Client'}</h2>;
return <div>
<AppNavbar/>
<Container>
{title}
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for="name">Name</Label>
<Input type="text" name="name" id="name" value={item.name || ''}
onChange={this.handleChange} autoComplete="name"/>
</FormGroup>
<FormGroup>
<Label for="email">Email</Label>
<Input type="text" name="email" id="email" value={item.email || ''}
onChange={this.handleChange} autoComplete="email"/>
</FormGroup>
<FormGroup>
<Button color="primary" type="submit">Save</Button>{' '}
<Button color="secondary" tag={Link} to="/clients">Cancel</Button>
</FormGroup>
</Form>
</Container>
</div>
}
Примечание. У нас также есть ссылка
с маршрутом, настроенным для возврата к /clients
при нажатии кнопки « Отмена
» .
3.6. Создание компонента AppNavbar
Чтобы сделать наше приложение более удобным для навигации , давайте создадим файл в frontend/src/AppNavbar.js
:
import React, {Component} from 'react';
import {Navbar, NavbarBrand} from 'reactstrap';
import {Link} from 'react-router-dom';
export default class AppNavbar extends Component {
constructor(props) {
super(props);
this.state = {isOpen: false};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
return <Navbar color="dark" dark expand="md">
<NavbarBrand tag={Link} to="/">Home</NavbarBrand>
</Navbar>;
}
}
В функции рендеринга
мы будем использовать возможности react-router-dom
для создания ссылки
для перехода на домашнюю
страницу нашего приложения .
3.7. Создание нашего домашнего
компонента
Этот компонент будет домашней
страницей нашего приложения и будет иметь кнопку для нашего ранее созданного компонента ClientList
.
Создадим файл в frontend/src/Home.js
:
import React, { Component } from 'react';
import './App.css';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';
import { Button, Container } from 'reactstrap';
class Home extends Component {
render() {
return (
<div>
<AppNavbar/>
<Container fluid>
<Button color="link"><Link to="/clients">Clients</Link></Button>
</Container>
</div>
);
}
}
export default Home;
Примечание. В этом компоненте у нас также есть ссылка
из react-router-dom
, которая ведет нас к /clients
. Этот маршрут будет настроен на следующем шаге.
3.8. Использование React-маршрутизатора
Теперь мы будем использовать React Router для навигации между нашими компонентами.
Давайте изменим наш App.js
:
import React, { Component } from 'react';
import './App.css';
import Home from './Home';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import ClientList from './ClientList';
import ClientEdit from "./ClientEdit";
class App extends Component {
render() {
return (
<Router>
<Switch>
<Route path='/' exact={true} component={Home}/>
<Route path='/clients' exact={true} component={ClientList}/>
<Route path='/clients/:id' component={ClientEdit}/>
</Switch>
</Router>
)
}
}
export default App;
Как мы видим, у нас есть маршруты приложений, определенные для каждого из созданных нами компонентов.
При доступе к localhost:3000 у нас теперь есть домашняя
страница со ссылкой « Клиенты »:
Нажав на ссылку « Клиенты
», у нас теперь есть список клиентов, а также функции « Редактировать
», « Удалить
» и « Добавить клиент
» :
4. Сборка и упаковка
Чтобы собрать и упаковать наше приложение React с помощью Maven , мы будем использовать frontend-maven-plugin
.
Этот плагин будет отвечать за упаковку и копирование нашего внешнего
приложения в нашу папку сборки Spring Boot API:
<properties>
...
<frontend-maven-plugin.version>1.6</frontend-maven-plugin.version>
<node.version>v14.8.0</node.version>
<yarn.version>v1.12.1</yarn.version>
...
</properties>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
...
</executions>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>${frontend-maven-plugin.version}</version>
<configuration>
...
</configuration>
<executions>
...
</executions>
</plugin>
...
</plugins>
</build>
Давайте подробнее рассмотрим наш maven-resources-plugin
, который отвечает за копирование исходников нашего интерфейса в
целевую
папку приложения:
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>process-classes</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/static</outputDirectory>
<resources>
<resource>
<directory>frontend/build</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
...
Затем наш front-end-maven-plugin
будет отвечать за установку Node.js
и Yarn , а затем за сборку и тестирование нашего внешнего
приложения:
...
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>${frontend-maven-plugin.version}</version>
<configuration>
<workingDirectory>frontend</workingDirectory>
</configuration>
<executions>
<execution>
<id>install node</id>
<goals>
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<yarnVersion>${yarn.version}</yarnVersion>
</configuration>
</execution>
<execution>
<id>yarn install</id>
<goals>
<goal>yarn</goal>
</goals>
<phase>generate-resources</phase>
</execution>
<execution>
<id>yarn test</id>
<goals>
<goal>yarn</goal>
</goals>
<phase>test</phase>
<configuration>
<arguments>test</arguments>
<environmentVariables>
<CI>true</CI>
</environmentVariables>
</configuration>
</execution>
<execution>
<id>yarn build</id>
<goals>
<goal>yarn</goal>
</goals>
<phase>compile</phase>
<configuration>
<arguments>build</arguments>
</configuration>
</execution>
</executions>
</plugin>
...
Примечание: чтобы указать другую версию Node.js, мы можем просто отредактировать свойство node.version
в нашем pom.xml
.
5. Запуск нашего приложения Spring Boot React CRUD
Наконец, добавив плагин, мы можем получить доступ к нашему приложению, выполнив:
mvn spring-boot:run
Наше приложение React будет полностью интегрировано в наш API по адресу http://localhost:8080/ .
6. Заключение
В этой статье мы рассмотрели, как создать CRUD-приложение с использованием Spring Boot и React. Для этого мы сначала создали несколько конечных точек REST API для взаимодействия с нашей базой данных. Затем мы создали несколько компонентов React для извлечения и записи данных с помощью нашего API. Мы также узнали, как упаковать наше приложение Spring Boot с нашим пользовательским интерфейсом React в единый пакет приложения.
Исходный код нашего приложения доступен на GitHub .