Перейти к основному содержимому

Приложение CRUD с React и Spring Boot

· 11 мин. чтения

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:

./4cfbc9c155d2086ba841bbc8316fc5fc.png

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.

Мы увидим страницу нашего клиента, которая будет выглядеть так:

./9dd6cca1f06dbe433028a07957998a2d.png

Примечание. Убедитесь, что приложение 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 у нас теперь есть домашняя страница со ссылкой « Клиенты »:

./1683af4410bd0d9429fdbed686876d02.png

Нажав на ссылку « Клиенты », у нас теперь есть список клиентов, а также функции « Редактировать », « Удалить » и « Добавить клиент » :

./abd0afed5aaa491801dac82cd8fab3d1.png

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 .