← Назад к вопросам
Реализация простого REST API с Spring Boot
1.8 Middle🔥 201 комментариев
#ORM и Hibernate#Spring Boot и Spring Data#Spring Framework
Условие
Создайте простое REST API для управления задачами (Todo List) с использованием Spring Boot.
Эндпоинты
- GET /api/todos — получить все задачи
- GET /api/todos/{id} — получить задачу по ID
- POST /api/todos — создать новую задачу
- PUT /api/todos/{id} — обновить задачу
- DELETE /api/todos/{id} — удалить задачу
Модель данных
class Todo {
Long id;
String title;
boolean completed;
}
Требования
- Используйте @RestController
- Возвращайте правильные HTTP статусы
- Используйте DTO для запросов/ответов
- Добавьте валидацию входных данных
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация простого REST API с Spring Boot
Это задача, которая проверяет владение Spring Boot и умение создавать производственный код с правильной обработкой ошибок, валидацией и правильными HTTP статусами.
Структура проекта
src/main/java/
├── com.example.todo/
│ ├── controller/
│ │ └── TodoController.java
│ ├── service/
│ │ └── TodoService.java
│ ├── repository/
│ │ └── TodoRepository.java
│ ├── entity/
│ │ └── Todo.java
│ ├── dto/
│ │ ├── CreateTodoRequest.java
│ │ ├── UpdateTodoRequest.java
│ │ └── TodoResponse.java
│ └── Application.java
1. Сущность Todo
package com.example.todo.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "todos")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private boolean completed = false;
@Column(name = "created_at")
private Long createdAt = System.currentTimeMillis();
}
2. DTO классы
package com.example.todo.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateTodoRequest {
@NotBlank(message = "Title cannot be empty")
@Size(min = 1, max = 255, message = "Title must be between 1 and 255 characters")
private String title;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UpdateTodoRequest {
@Size(min = 1, max = 255, message = "Title must be between 1 and 255 characters")
private String title;
private Boolean completed;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodoResponse {
private Long id;
private String title;
private boolean completed;
private Long createdAt;
}
3. Repository
package com.example.todo.repository;
import com.example.todo.entity.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TodoRepository extends JpaRepository<Todo, Long> {
}
4. Service (бизнес-логика)
package com.example.todo.service;
import com.example.todo.dto.CreateTodoRequest;
import com.example.todo.dto.TodoResponse;
import com.example.todo.dto.UpdateTodoRequest;
import com.example.todo.entity.Todo;
import com.example.todo.repository.TodoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class TodoService {
private final TodoRepository todoRepository;
/**
* Получить все задачи
*/
public List<TodoResponse> getAllTodos() {
return todoRepository.findAll()
.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
/**
* Получить задачу по ID
*/
public TodoResponse getTodoById(Long id) {
Todo todo = todoRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Todo not found with id: " + id));
return convertToResponse(todo);
}
/**
* Создать новую задачу
*/
public TodoResponse createTodo(CreateTodoRequest request) {
Todo todo = new Todo();
todo.setTitle(request.getTitle());
todo.setCompleted(false);
Todo saved = todoRepository.save(todo);
return convertToResponse(saved);
}
/**
* Обновить задачу
*/
public TodoResponse updateTodo(Long id, UpdateTodoRequest request) {
Todo todo = todoRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Todo not found with id: " + id));
if (request.getTitle() != null && !request.getTitle().isBlank()) {
todo.setTitle(request.getTitle());
}
if (request.getCompleted() != null) {
todo.setCompleted(request.getCompleted());
}
Todo updated = todoRepository.save(todo);
return convertToResponse(updated);
}
/**
* Удалить задачу
*/
public void deleteTodo(Long id) {
if (!todoRepository.existsById(id)) {
throw new ResourceNotFoundException("Todo not found with id: " + id);
}
todoRepository.deleteById(id);
}
/**
* Конвертация сущности в DTO
*/
private TodoResponse convertToResponse(Todo todo) {
return new TodoResponse(
todo.getId(),
todo.getTitle(),
todo.isCompleted(),
todo.getCreatedAt()
);
}
}
5. Exception Handler
package com.example.todo.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(
ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
ex.getMessage(),
System.currentTimeMillis()
);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
String message = ex.getBindingResult()
.getAllErrors()
.stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining(", "));
ErrorResponse error = new ErrorResponse(
"VALIDATION_ERROR",
message,
System.currentTimeMillis()
);
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
ErrorResponse error = new ErrorResponse(
"INTERNAL_ERROR",
"An unexpected error occurred",
System.currentTimeMillis()
);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(error);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class ErrorResponse {
private String code;
private String message;
private Long timestamp;
}
6. REST Controller
package com.example.todo.controller;
import com.example.todo.dto.CreateTodoRequest;
import com.example.todo.dto.TodoResponse;
import com.example.todo.dto.UpdateTodoRequest;
import com.example.todo.service.TodoService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/todos")
@RequiredArgsConstructor
public class TodoController {
private final TodoService todoService;
/**
* GET /api/todos — получить все задачи
*/
@GetMapping
public ResponseEntity<List<TodoResponse>> getAllTodos() {
List<TodoResponse> todos = todoService.getAllTodos();
return ResponseEntity.ok(todos);
}
/**
* GET /api/todos/{id} — получить задачу по ID
*/
@GetMapping("/{id}")
public ResponseEntity<TodoResponse> getTodoById(@PathVariable Long id) {
TodoResponse todo = todoService.getTodoById(id);
return ResponseEntity.ok(todo);
}
/**
* POST /api/todos — создать новую задачу
*/
@PostMapping
public ResponseEntity<TodoResponse> createTodo(
@Valid @RequestBody CreateTodoRequest request) {
TodoResponse todo = todoService.createTodo(request);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(todo);
}
/**
* PUT /api/todos/{id} — обновить задачу
*/
@PutMapping("/{id}")
public ResponseEntity<TodoResponse> updateTodo(
@PathVariable Long id,
@Valid @RequestBody UpdateTodoRequest request) {
TodoResponse todo = todoService.updateTodo(id, request);
return ResponseEntity.ok(todo);
}
/**
* DELETE /api/todos/{id} — удалить задачу
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTodo(@PathVariable Long id) {
todoService.deleteTodo(id);
return ResponseEntity.noContent().build();
}
}
7. Application.java
package com.example.todo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
8. application.properties
spring.application.name=todo-api
server.port=8080
# Database
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# JPA/Hibernate
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=false
# Logging
logging.level.root=INFO
logging.level.com.example.todo=DEBUG
9. pom.xml (зависимости)
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
10. Тестирование с curl
# Получить все задачи (пусто)
curl http://localhost:8080/api/todos
# Создать задачу
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title":"Купить хлеб"}'
# Получить задачу по ID
curl http://localhost:8080/api/todos/1
# Обновить задачу
curl -X PUT http://localhost:8080/api/todos/1 \
-H "Content-Type: application/json" \
-d '{"title":"Купить молоко","completed":true}'
# Удалить задачу
curl -X DELETE http://localhost:8080/api/todos/1
# Ошибка валидации (пустой title)
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"title":""}'
# Ошибка 404
curl http://localhost:8080/api/todos/999
Ключевые особенности решения
- @RestController — автоматически сериализует ответы в JSON
- @Valid — активирует валидацию на уровне контроллера
- HTTP статусы:
- 200 OK — успешный GET/PUT
- 201 CREATED — успешный POST
- 204 NO CONTENT — успешный DELETE
- 400 BAD REQUEST — ошибка валидации
- 404 NOT FOUND — ресурс не найден
- DTO — отделяют внутреннее представление от внешнего API
- Service — содержит бизнес-логику
- Repository — только работа с БД
- Exception Handler — централизованная обработка ошибок
Это решение соответствует лучшим практикам Spring Boot и REST API дизайна.