GraphQL (Spring Boot)

1. Introduction

GraphQL is a query language for APIs and a runtime for fulfilling those queries. It provides a complete and understandable description of the data in your API and it gives clients the power to ask for exactly what they need.


GraphiQL is an in-browser tool for writing, validating, and testing GraphQL queries.


2. Reference

  • GraphQL

https://graphql.org/

https://graphql.org/learn/queries/ [Queries and Mutations]

  • Getting Started With GraphQL SPQR and Spring Boot

https://www.baeldung.com/spring-boot-graphql-spqr

  • How to retrieve a GraphQL schema

https://medium.com/@mrthankyou/how-to-get-a-graphql-schema-28915025de0e


3. GraphQL schema

3.1. Export schema

Schema can be retrieved by introspection from the GraphQL endpoint.

Eg: Executed at GraphiQL

fragment FullType on __Type {

kind

name

fields(includeDeprecated: true) {

name

args {

...InputValue

}

type {

...TypeRef

}

isDeprecated

deprecationReason

}

inputFields {

...InputValue

}

interfaces {

...TypeRef

}

enumValues(includeDeprecated: true) {

name

isDeprecated

deprecationReason

}

possibleTypes {

...TypeRef

}

}

fragment InputValue on __InputValue {

name

type {

...TypeRef

}

defaultValue

}

fragment TypeRef on __Type {

kind

name

ofType {

kind

name

ofType {

kind

name

ofType {

kind

name

ofType {

kind

name

ofType {

kind

name

ofType {

kind

name

ofType {

kind

name

}

}

}

}

}

}

}

}

query IntrospectionQuery {

__schema {

queryType {

name

}

mutationType {

name

}

types {

...FullType

}

directives {

name

locations

args {

...InputValue

}

}

}

}

3.1. Import schema

TBD, don't know yet.

4. API clients

4.1. Insomnia

See article https://sites.google.com/site/pawneecity/insomnia


4.1. GraphiQL

See page https://www.electronjs.org/apps/graphiql


5. Spring Boot example

Note: At time of writing Spring Boot 2.7.0 was just released, but graphql-spqr-spring-boot-starter v0.0.6 was not yet compatible. Therefore, this example uses Spring Boot 2.6.8.

5.1. Source code

Makefile

.PHONY: help run


# ========================================================

# WARNING: This file is for development purposes only.

# Do NOT use it for anything else (it can be modified or deleted at any time w/out previous notice).

# You've been warned!

# ========================================================



# CONFIG




help:

@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'



clean: ## Clean

./mvnw clean



package: ## Package (implies clean; skip Tests)

./mvnw clean package -DskipTests=true


verify: ## Verify

./mvnw verify \

2>&1 | tee runTests-nogit.out



run: ## Run, skip ITs

./mvnw spring-boot:run -DskipITs=true



all:

make clean

make run


pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<!-- graphql-spqr-spring-boot-starter v0.0.6 not yet compatible w/ sb-2.7.0 -->

<version>2.6.8</version>

<relativePath /> <!-- lookup parent from repository -->

</parent>

<groupId>org.jota</groupId>

<artifactId>graphql</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>graphql</name>

<description>PoC Spring Boot with GraphQL</description>

<properties>

<java.version>17</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter</artifactId>

</dependency>


<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>


<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>


<!-- Requires spring-boot-starter-web -->

<dependency>

<groupId>io.leangen.graphql</groupId>

<artifactId>graphql-spqr-spring-boot-starter</artifactId>

<version>0.0.6</version>

</dependency>


<!-- GraphiQL -->

<dependency>

<groupId>com.graphql-java-kickstart</groupId>

<artifactId>graphiql-spring-boot-starter</artifactId>

<version>11.1.0</version>

</dependency>


<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<scope>provided</scope>

</dependency>

</dependencies>


<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>


</project>


src/main/java/org/jota/graphql/book/Book.java

package org.jota.graphql.book;


import lombok.Data;

/**

*

*/

@Data

public class Book {

private Integer id;

private String author;

private String title;

}


src/main/java/org/jota/graphql/book/BookResolver.java

package org.jota.graphql.service;


import java.util.List;


import org.jota.graphql.book.Book;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;


import io.leangen.graphql.annotations.GraphQLArgument;

import io.leangen.graphql.annotations.GraphQLMutation;

import io.leangen.graphql.annotations.GraphQLQuery;


/**

* Exposing the Service with graphql-spqr.

*/

@Service

public class BookResolver {


@Autowired

BookServiceI bookService;


@GraphQLQuery(name = "getBookWithTitle")

public Book getBookWithTitle(@GraphQLArgument(name = "title") String title) {

return bookService.getBookWithTitle(title);

}


@GraphQLQuery(name = "getAllBooks", description = "Get all books")

public List<Book> getAllBooks() {

return bookService.getAllBooks();

}


@GraphQLMutation(name = "addBook")

public Book addBook(@GraphQLArgument(name = "newBook") Book book) {

return bookService.addBook(book);

}


@GraphQLMutation(name = "updateBook")

public Book updateBook(@GraphQLArgument(name = "modifiedBook") Book book) {

return bookService.updateBook(book);

}


@GraphQLMutation(name = "deleteBook")

public void deleteBook(@GraphQLArgument(name = "book") Book book) {

bookService.deleteBook(book);

}

}


src/main/java/org/jota/graphql/book/BookServiceI.java

package org.jota.graphql.service;


import java.util.HashSet;

import java.util.List;

import java.util.Set;

import java.util.stream.Collectors;


import org.jota.graphql.book.Book;

import org.springframework.stereotype.Service;


import io.leangen.graphql.annotations.GraphQLMutation;

import io.leangen.graphql.annotations.GraphQLQuery;

import io.leangen.graphql.spqr.spring.annotations.GraphQLApi;


/**

* We don't need the GraphqlController class: a /graphql endpoint will be added automatically.

*/

@GraphQLApi

@Service

public class BookService implements BookServiceI {


private Set<Book> books = new HashSet<>();


@GraphQLQuery(name = "getBookWithTitle")

public Book getBookWithTitle(String title) {

return books.stream().filter(book -> book.getTitle().equals(title)).findFirst().orElse(null);

}


@GraphQLQuery(name = "getAllBooks", description = "Get all books")

public List<Book> getAllBooks() {

return books.stream().collect(Collectors.toList());

}


@GraphQLMutation(name = "addBook")

public Book addBook(Book book) {

books.add(book);

return book;

}


@GraphQLMutation(name = "updateBook")

public Book updateBook(Book book) {

books.remove(book);

books.add(book);

return book;

}


@GraphQLMutation(name = "deleteBook")

public boolean deleteBook(Book book) {

return books.remove(book);

}


}


src/main/java/org/jota/graphql/book/BookService.java

package org.jota.graphql.service;


import java.util.List;


import org.jota.graphql.book.Book;


public interface BookServiceI {

Book getBookWithTitle(String title);


List<Book> getAllBooks();


Book addBook(Book book);


Book updateBook(Book book);


boolean deleteBook(Book book);

}



5.2. Invocation

Requests can be sent using a client like cURL, Insomnia or GraphiQL consuming the endpoint:

http://localhost:8080/graphiql


Mutation invocation example

Using GraphiQL Query:

mutation CreateBook($book: BookInput) {

book: addBook(book: $book) {

id

title

}

}

w/ variables:

{

"book": {

"id": 28,

"author": "John 28",

"title": "Talk 28"

}

}

Using cURL

curl -g \

-X POST \

-H "Content-Type: application/json" \

-d '{"query":"mutation CreateBook($book: BookInput) { book: addBook(book: $book) { id title }}","variables":{"book":{"id":28,"author":"John 28","title":"Talk 28"}},"operationName":"CreateBook"}' \

http://localhost:8080/graphql

Response:

{

"data": {

"book": {

"id": 28,

"title": "Talk 28"

}

}

}


Query invocation example

Using GraphiQL Query:

query ListBooks {

myBooks: getAllBooks {

id

author

title

}

}

w/ variables:


Using cURL

curl -g \

-X POST \

-H "Content-Type: application/json" \

-d '{"query": "query ListBooks {myBooks: getAllBooks {id author title }}"}' \

http://localhost:8080/graphql

Response:

{

"data": {

"myBooks": [

{

"id": 28,

"author": "John 28",

"title": "Talk 28"

},

{

"id": 27,

"author": "John Doe",

"title": "I talk"

}

]

}

}