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/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"
}
]
}
}