Implementare una API GraphQL di base con Spring Boot

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:
    • books restituisce tutti i libri;
    • bookById restituisce un singolo libro dato il suo id.
  • Mutation: le operazioni di modifica:
    • addBook crea 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 Author con 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 @QueryMapping e @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.

Torna su