Implementare una semplice API REST con Spring Boot

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.

  1. Vai su Spring Initializr.
  2. Configura il progetto:
    • Project: Maven
    • Language: Java
    • Spring Boot: versione stabile più recente
    • Group: com.example
    • Artifact: book-api
  3. Dependencies:
    • Spring Web
    • Spring Boot DevTools (opzionale ma comodo in sviluppo)
  4. 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.validation e annotazioni come @NotNull e @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.

Torna su