1. Базовий REST-контролер
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService service;
@GetMapping
public List<UserDto> getAll() {
return service.allUsers();
}
@GetMapping("/{id}")
public UserDto getById(@PathVariable Long id) {
return service.getUser(id);
}
@PostMapping
public UserDto create(@Valid @RequestBody UserDto dto) {
return service.create(dto);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
service.delete(id);
}
}📌 Аннотації:
- @RestController = @Controller + @ResponseBody
- @RequestMapping → базовий префікс (/api/users)
- @GetMapping, @PostMapping, @PutMapping, @DeleteMapping
2. Валідація (JSR-380 Bean Validation)
📄 DTO:
public record UserDto(
@NotBlank String name,
@Email String email,
@Min(18) int age
) {}📄 У контролері:
@PostMapping
public UserDto create(@Valid @RequestBody UserDto dto) {
return service.create(dto);
}📄 Обробка помилок:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors()
.forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors);
}
}3. Обробка помилок (ProblemDetail — Spring 6, RFC 7807)
@RestControllerAdvice
class ApiExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
ProblemDetail handleIllegalArgument(IllegalArgumentException e) {
ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
pd.setTitle("Validation failed");
pd.setDetail(e.getMessage());
return pd;
}
}📌 Відповідь JSON буде у форматі:
{
"type": "about:blank",
"title": "Validation failed",
"status": 400,
"detail": "Invalid email"
}4. Пагінація і сортування
Spring Data JPA вміє сам:
@GetMapping
public Page<UserDto> all(@RequestParam int page, @RequestParam int size) {
return repo.findAll(PageRequest.of(page, size, Sort.by("name").ascending()));
}📌 JSON-відповідь буде з полями:
{
"content": [...],
"totalPages": 5,
"totalElements": 100,
"number": 0,
"size": 20
}5. Фільтрація (Specification API)
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {}
@GetMapping("/search")
public List<User> search(@RequestParam(required=false) String name,
@RequestParam(required=false) Integer minAge) {
return repo.findAll((root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (name != null) predicates.add(cb.like(root.get("name"), "%" + name + "%"));
if (minAge != null) predicates.add(cb.greaterThanOrEqualTo(root.get("age"), minAge));
return cb.and(predicates.toArray(new Predicate[0]));
});
}6. Завантаження, скачування файлів
Upload
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) throws IOException {
Path path = Paths.get("uploads/" + file.getOriginalFilename());
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return "Uploaded: " + path;
}Download
@GetMapping("/download/{filename}")
public ResponseEntity<Resource> download(@PathVariable String filename) throws IOException {
Path path = Paths.get("uploads/" + filename);
Resource resource = new UrlResource(path.toUri());
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
.body(resource);
}7. Rate Limiting (обмеження запитів)
📄 Простий варіант (Resilience4j + Spring Boot Starter):
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>📄 Конфіг:
resilience4j.ratelimiter:
instances:
apiLimiter:
limitForPeriod: 5
limitRefreshPeriod: 10s
timeoutDuration: 0📄 Використання:
@RestController
@RequiredArgsConstructor
public class DemoController {
@RateLimiter(name = "apiLimiter")
@GetMapping("/limited")
public String limited() {
return "OK";
}
}8. Документація (Swagger, OpenAPI)
📄 Додаємо залежність:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>📌 Тепер доступно:
- Swagger UI → http://localhost:8080/swagger-ui.html
- OpenAPI JSON → http://localhost:8080/v3/api-docs
9. CORS (Cross-Origin Resource Sharing)
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET","POST","PUT","DELETE");
}
};
}
}10. Best Practices
- ✅ Використовуй DTO + @Valid замість Entity напряму.
- ✅ Обробляй помилки через **@**RestControllerAdvice.
- ✅ Завжди додавай пагінацію/сортування у GET-запити.
- ✅ Для API краще застосовувати ProblemDetail (RFC 7807).
- ✅ Документуй API через Swagger**/OpenAPI**.
- ✅ Додавай rate limiting для захисту.