In questo articolo vediamo, passo dopo passo, come costruire una piccola API GraphQL con Spring Boot. L'obiettivo è esporre un semplice catalogo di libri, con operazioni di lettura (query) e creazione (mutation), usando solo gli strumenti di base di Spring Boot 3 e Spring for GraphQL.
1. Che cosa è GraphQL e perché usarlo con Spring Boot
GraphQL è un linguaggio di query per API che consente ai client di specificare esattamente i dati
di cui hanno bisogno. A differenza delle API REST classiche, che espongono molteplici endpoint
(per esempio /books, /books/1, /authors), un'API GraphQL ha tipicamente
un unico endpoint (per esempio /graphql) e un schema fortemente tipizzato che descrive i
tipi e le operazioni disponibili.
Spring Boot integra GraphQL attraverso lo starter spring-boot-starter-graphql, che aggiunge:
- la configurazione dell'endpoint
/graphql; - il mapping tra schema GraphQL e metodi Java tramite annotazioni;
- opzionalmente una console di test (GraphiQL o simili).
2. Requisiti di base
Per seguire l'esempio ti serviranno:
- Java 17 o superiore;
- Maven o Gradle (qui useremo Maven);
- Spring Boot 3.x;
- Un editor/IDE a scelta (IntelliJ IDEA, Eclipse, VS Code, ecc.).
3. Creazione del progetto Spring Boot
Puoi creare il progetto da start.spring.io, selezionando:
- Project: Maven;
- Language: Java;
- Spring Boot: 3.x;
- Dependencies: Spring Web, Spring for GraphQL, Lombok (opzionale).
Oppure puoi aggiungere manualmente le dipendenze al tuo pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4. Modello di dominio: entità Book
Creiamo una semplice entità Book con tre campi: id, title e author.
Per semplicità useremo una collezione in memoria invece di un database.
package com.example.graphql.book;
public class Book {
private Long id;
private String title;
private String author;
public Book() {
}
public Book(Long id, String title, String author) {
this.id = id;
this.title = title;
this.author = author;
}
public Long getId() {
return id;
}
public Book setId(Long id) {
this.id = id;
return this;
}
public String getTitle() {
return title;
}
public Book setTitle(String title) {
this.title = title;
return this;
}
public String getAuthor() {
return author;
}
public Book setAuthor(String author) {
this.author = author;
return this;
}
}
Se usi Lombok puoi ridurre il boilerplate con le annotazioni @Data, @NoArgsConstructor e
@AllArgsConstructor.
package com.example.graphql.book;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private Long id;
private String title;
private String author;
}
5. Repository in memoria
Creiamo un piccolo repository che gestisce una lista di libri in memoria. In un progetto reale potresti usare Spring Data JPA e un database, ma l'idea di base non cambia.
package com.example.graphql.book;
import org.springframework.stereotype.Repository;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
@Repository
public class BookRepository {
private final Map<Long, Book> books = new HashMap<>();
private final AtomicLong sequence = new AtomicLong(0);
public BookRepository() {
save(new Book(null, "Clean Code", "Robert C. Martin"));
save(new Book(null, "Effective Java", "Joshua Bloch"));
}
public List<Book> findAll() {
return new ArrayList<>(books.values());
}
public Optional<Book> findById(Long id) {
return Optional.ofNullable(books.get(id));
}
public Book save(Book book) {
if (book.getId() == null) {
book.setId(sequence.incrementAndGet());
}
books.put(book.getId(), book);
return book;
}
}
6. Definire lo schema GraphQL
Spring for GraphQL cerca i file di schema con estensione .graphqls in
src/main/resources/graphql/. Creiamo quindi il file
src/main/resources/graphql/schema.graphqls con il seguente contenuto:
type Book {
id: ID!
title: String!
author: String!
}
type Query {
books: [Book!]!
bookById(id: ID!): Book
}
type Mutation {
addBook(title: String!, author: String!): Book!
}
In questo schema definiamo:
- Book: il tipo che rappresenta un libro;
- Query: le operazioni di lettura:
booksrestituisce tutti i libri;bookByIdrestituisce un singolo libro dato il suo id.
- Mutation: le operazioni di modifica:
addBookcrea un nuovo libro.
7. Service e controller GraphQL
Per mantenere il codice ordinato, aggiungiamo un BookService che incapsula la logica
applicativa e poi un controller GraphQL che mappa le query e le mutation dello schema sui
metodi Java.
7.1 BookService
package com.example.graphql.book;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookService {
private final BookRepository repository;
public BookService(BookRepository repository) {
this.repository = repository;
}
public List<Book> getAllBooks() {
return repository.findAll();
}
public Book getBookById(Long id) {
return repository.findById(id)
.orElse(null);
}
public Book addBook(String title, String author) {
Book book = new Book(null, title, author);
return repository.save(book);
}
}
7.2 GraphQL controller
Il controller GraphQL sfrutta le annotazioni @QueryMapping e @MutationMapping per collegare
le operazioni definite nello schema ai metodi Java. I parametri delle query vengono mappati con
@Argument.
package com.example.graphql.book;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller
public class BookGraphQLController {
private final BookService service;
public BookGraphQLController(BookService service) {
this.service = service;
}
@QueryMapping
public List<Book> books() {
return service.getAllBooks();
}
@QueryMapping
public Book bookById(@Argument Long id) {
return service.getBookById(id);
}
@MutationMapping
public Book addBook(@Argument String title, @Argument String author) {
return service.addBook(title, author);
}
}
I nomi dei metodi (books, bookById, addBook) corrispondono esattamente ai nomi
delle operazioni nello schema GraphQL. Spring si occupa di fare il collegamento.
8. Configurazione dell'endpoint GraphQL
Per impostazione predefinita, lo starter abilita l'endpoint /graphql su HTTP POST. Possiamo
aggiungere alcune proprietà in src/main/resources/application.yml per abilitare GraphiQL,
una semplice interfaccia web per testare le query:
spring:
graphql:
path: /graphql
graphiql:
enabled: true
path: /graphiql
Ora, avviando l'applicazione Spring Boot, dovresti poter aprire
http://localhost:8080/graphiql nel browser e provare le query direttamente da lì.
9. Esempi di query e mutation
9.1 Ottenere l'elenco dei libri
Esegui la seguente query GraphQL:
query {
books {
id
title
author
}
}
Otterrai una risposta simile alla seguente (il formato effettivo dipende dai dati presenti nel repository):
{
"data": {
"books": [
{
"id": "1",
"title": "Clean Code",
"author": "Robert C. Martin"
},
{
"id": "2",
"title": "Effective Java",
"author": "Joshua Bloch"
}
]
}
}
9.2 Recuperare un libro per id
query {
bookById(id: 1) {
id
title
author
}
}
Se esiste un libro con id 1, verrà restituito; in caso contrario, il campo
bookById sarà null.
9.3 Creare un nuovo libro
Per aggiungere un libro, usa la mutation definita nello schema:
mutation {
addBook(title: "Domain-Driven Design", author: "Eric Evans") {
id
title
author
}
}
Dovresti vedere in risposta il nuovo libro con un id generato automaticamente. Se ora rilanci la
query books, il nuovo elemento sarà incluso nell'elenco.
10. Test dell'API senza interfaccia web
Se preferisci testare l'API da riga di comando, puoi usare curl e inviare una richiesta
POST a /graphql con un payload JSON che contiene la query.
curl -X POST http://localhost:8080/graphql -H "Content-Type: application/json" -d '{ "query": "query { books { id title author } }" }'
La risposta sarà un JSON con lo stesso formato visto in precedenza. In un ambiente di produzione, spesso si utilizza un client HTTP (per esempio Postman, Insomnia o similari) o librerie client GraphQL specifiche per il linguaggio utilizzato dal front-end.
11. Estensioni possibili
Una volta compresa la struttura di base, puoi estendere facilmente l'API GraphQL. Alcune idee:
- Aggiungere tipi correlati, per esempio
Authorcon relazioni tra libri e autori. - Integrare un database relazionale tramite Spring Data JPA.
- Gestire errori personalizzati e validazione dei dati in input.
- Aggiungere filtri, ordinamenti e paginazione alle query.
- Implementare autenticazione e autorizzazione (per esempio con Spring Security).
Grazie all'integrazione di Spring Boot e Spring for GraphQL, passare da una semplice API di prova a un backend GraphQL completo è per lo più una questione di progettare bene lo schema e i servizi che lo supportano.
12. Conclusioni
In questo articolo abbiamo visto come:
- creare un progetto Spring Boot con supporto GraphQL;
- definire un modello di dominio e un repository;
- descrivere uno schema GraphQL in un file
.graphqls; - collegare schema e codice Java con
@QueryMappinge@MutationMapping; - testare le query tramite GraphiQL o strumenti da riga di comando.
Queste basi sono sufficienti per iniziare a progettare e sviluppare API GraphQL più complesse con Spring Boot, mantenendo il codice leggibile e facilmente estendibile.