In questo articolo vediamo come creare passo per passo una semplice API REST con Spring Boot. Realizzeremo un piccolo servizio per gestire un catalogo di libri, con operazioni CRUD (Create, Read, Update, Delete) esposte tramite HTTP.
1. Creazione del progetto Spring Boot
Il modo più semplice per creare un nuovo progetto Spring Boot è usare Spring Initializr.
- Vai su Spring Initializr.
- Configura il progetto:
- Project: Maven
- Language: Java
- Spring Boot: versione stabile più recente
- Group:
com.example - Artifact:
book-api
- Dependencies:
- Spring Web
- Spring Boot DevTools (opzionale ma comodo in sviluppo)
- Genera e scarica il progetto, poi importalo nel tuo IDE.
2. Struttura di base del progetto
Il progetto generato conterrà un file pom.xml (per Maven) con le dipendenze di base.
Un esempio semplificato potrebbe essere:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Il punto di ingresso dell'applicazione è la classe annotata con
@SpringBootApplication, tipicamente simile a questa:
package com.example.bookapi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BookApiApplication {
public static void main(String[] args) {
SpringApplication.run(BookApiApplication.class, args);
}
}
3. Modellare il dominio: la classe Book
Iniziamo definendo l'entità principale della nostra API, un semplice Book
con alcuni campi rappresentativi.
package com.example.bookapi.model;
public class Book {
private Long id;
private String title;
private String author;
private int year;
public Book() {
}
public Book(Long id, String title, String author, int year) {
this.id = id;
this.title = title;
this.author = author;
this.year = year;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
4. Repository in memoria
Per semplicità useremo un repository in memoria basato su Map. In un'applicazione reale
potresti usare Spring Data JPA con un database relazionale o NoSQL.
package com.example.bookapi.repository;
import com.example.bookapi.model.Book;
import org.springframework.stereotype.Repository;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
@Repository
public class BookRepository {
private final Map<Long, Book> storage = new HashMap<>();
private final AtomicLong idGenerator = new AtomicLong(0);
public List<Book> findAll() {
return new ArrayList<>(storage.values());
}
public Optional<Book> findById(Long id) {
return Optional.ofNullable(storage.get(id));
}
public Book save(Book book) {
if (book.getId() == null) {
book.setId(idGenerator.incrementAndGet());
}
storage.put(book.getId(), book);
return book;
}
public void deleteById(Long id) {
storage.remove(id);
}
public boolean existsById(Long id) {
return storage.containsKey(id);
}
}
5. Controller REST
Il controller è il punto di contatto tra il mondo HTTP e il nostro dominio. Useremo le annotazioni di Spring MVC per mappare i vari endpoint.
package com.example.bookapi.controller;
import com.example.bookapi.model.Book;
import com.example.bookapi.repository.BookRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/books")
public class BookController {
private final BookRepository repository;
public BookController(BookRepository repository) {
this.repository = repository;
}
@GetMapping
public List<Book> getAll() {
return repository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Book> getById(@PathVariable Long id) {
return repository.findById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Book> create(@RequestBody Book book) {
Book created = repository.save(book);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<Book> update(@PathVariable Long id, @RequestBody Book book) {
if (!repository.existsById(id)) {
return ResponseEntity.notFound().build();
}
book.setId(id);
Book updated = repository.save(book);
return ResponseEntity.ok(updated);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
if (!repository.existsById(id)) {
return ResponseEntity.notFound().build();
}
repository.deleteById(id);
return ResponseEntity.noContent().build();
}
}
6. Testare l'API
Avvia l'applicazione eseguendo la classe BookApiApplication oppure usando Maven:
mvn spring-boot:run
Di default, l'applicazione sarà disponibile all'indirizzo http://localhost:8080.
Possiamo testare i vari endpoint usando curl, Postman o un qualsiasi client HTTP.
6.1. Ottenere tutti i libri
curl -X GET http://localhost:8080/api/books
6.2. Creare un nuovo libro
curl -X POST http://localhost:8080/api/books -H "Content-Type: application/json" -d '{
"title": "Clean Code",
"author": "Robert C. Martin",
"year": 2008
}'
La risposta conterrà il libro completo di id assegnato:
{
"id": 1,
"title": "Clean Code",
"author": "Robert C. Martin",
"year": 2008
}
6.3. Recuperare un libro esistente
curl -X GET http://localhost:8080/api/books/1
6.4. Aggiornare un libro
curl -X PUT http://localhost:8080/api/books/1 -H "Content-Type: application/json" -d '{
"title": "Clean Code (Updated)",
"author": "Robert C. Martin",
"year": 2009
}'
6.5. Eliminare un libro
curl -X DELETE http://localhost:8080/api/books/1
7. Gestione semplice degli errori
Nel controller abbiamo già usato ResponseEntity e codici di stato HTTP adeguati
(ad esempio 404 Not Found e 201 Created).
Per casi più avanzati si possono usare eccezioni personalizzate e @ControllerAdvice.
Ecco un esempio di eccezione personalizzata e relativa gestione globale semplificata.
package com.example.bookapi.exception;
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException(Long id) {
super("Book not found with id: " + id);
}
}
package com.example.bookapi.controller;
import com.example.bookapi.exception.BookNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BookNotFoundException.class)
public ResponseEntity<String> handleBookNotFound(BookNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
}
Ora, invece di restituire manualmente 404 nel controller, possiamo lanciare
l'eccezione:
@GetMapping("/{id}")
public Book getById(@PathVariable Long id) {
return repository.findById(id)
.orElseThrow(() -> new BookNotFoundException(id));
}
8. Struttura finale del progetto
Una possibile struttura dei package potrebbe essere:
src
└─ main
└─ java
└─ com.example.bookapi
├─ BookApiApplication.java
├─ model
│ └─ Book.java
├─ repository
│ └─ BookRepository.java
├─ controller
│ ├─ BookController.java
│ └─ GlobalExceptionHandler.java
└─ exception
└─ BookNotFoundException.java
9. Passi successivi
A questo punto hai una API REST di base funzionante. Da qui puoi:
- Integrare un database con Spring Data JPA.
- Aggiungere validazione dei dati con
javax.validatione annotazioni come@NotNulle@Size. - Documentare l'API con OpenAPI/Swagger.
- Aggiungere sicurezza con Spring Security.
Spring Boot rende molto semplice partire in fretta, lasciandoti concentrare sulla logica di business. Con pochi file hai definito un servizio REST completo, espandibile e pronto per essere integrato in applicazioni più complesse.