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

DGS framework (Netflix)

It makes easy to create GraphQL services with Sopring Boot

https://netflix.github.io/dgs/


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"

      }

    ]

  }

}