Implementing GraphQL server with Spring Boot GraphQL Server.

Background

GraphQL provides the flexibility for clients to retrieve fields that are only relevant to them. In this tutorial we will explore how we can implement GraphQL server with Spring Boot.

Server Implementation

schema.graphqls

scalar Long

type Query {
    findAll: [Book]
    findByTitle(title: String): Book
}

type Book {
    isbn: Isbn
    title: String
    author: Author
}

type Isbn {
    ean: Long
    registrationGroup: Int
    registrant: Int
    publication: Int
    digit: Int
}

type Author {
    name: Name
}

type Name {
    first: String
    last: String
}

This is the schema that inform our clients on available fields and queries. Next is to implement a @Controller that handle all requests.

Controller

@Controller
class BookResource {

    private final BookRepository repository;

    BookResource(BookRepository repository) {
        this.repository = repository;
    }

    @QueryMapping
    public List<Book> findAll() {
        return repository.findAll();
    }

    @QueryMapping
    public Book findByTitle(@Argument String title) {
        return repository.findByTitle(title);
    }

}

BookResource implements two types of services - findAll, a service without parameter and findByTitle, a service with a parameter. Both services are annotated with @QueryMapping to indicate that they are GraphQL queries.

Verification

We will utilise @GraphQlTest to verify our implementation.

Integration Test

@GraphQlTest(
        controllers = BookResource.class,
        includeFilters = @Filter(type = ANNOTATION, classes = { Configuration.class, Repository.class })
)
class BookResourceTests {

    @Autowired
    private GraphQlTester client;

    @Test
    @DisplayName("Given there are 3 books, when findAll is invoked, then return all books")
    void findAll() {
        client.documentName("books")
                .execute()
                .path("findAll")
                .matchesJson(
                        """
                                        [
                                              {
                                                "title": "Clean Code"
                                              },
                                              {
                                                "title": "Design Patterns"
                                              },
                                              {
                                                "title": "The Hobbit"
                                              }
                                            ]
                                """
                )
        ;
    }

    @Test
    @DisplayName("Given there is a book titled The Hobbit, when findByTitle is invoked, then return the book")
    void findByTitle() {
        client.documentName("books")
                .variable("title", "The Hobbit")
                .execute()
                .path("findByTitle")
                .matchesJson(
                        """
                                {
                                  "title": "The Hobbit",
                                  "isbn": {
                                    "ean": 9780132350884,
                                    "registrationGroup": 978,
                                    "registrant": 0,
                                    "publication": 13235088,
                                    "digit": 4
                                  },
                                  "author": {
                                    "name": {
                                      "first": "J.R.R.",
                                      "last": "Tolkien"
                                    }
                                  }
                                }
                                """
                );

Client schema definition

In order to map the response to a Java object, we need to define the schema of the response. This is done in books.graphl.

query books($title: String) {
  findByTitle(title: $title) {
    title
    isbn {
      ean
      registrationGroup
      registrant
      publication
      digit
    }
    author {
      name {
        first
        last
      }
    }
  }

  findAll {
    title
  }

}

By executing tests implemented in BookResourceTests, we can verify that our implementation is working as expected.