How to use GraphQL in Spring Boot

Nowadays web developers put a lot of effort to design a good API. It’s a key to build a robust and performant application. All discussions come down to question what is a true REST-ful API, should we use HATEOAS or how to ensure safety using REST through HTTP protocol.

Everything revolves around REST. Some projects still use SOAP protocol, but REST specification is the one which is in the limelight.

Questions are – does it fit every use case and are there any serious alternatives? In this tutorial, we will take a closer look at this topic.

Table of contents:
1. REST issues
2. GraphQL – REST alternative
2.1 Writing schema
2.2 Implementation in Spring Boot
2.3 How to change data – mutations
2.4 Let’s annotate this – GraphQL SPQR
2.5 Testing queries and mutations
3. Bad parts
4. Conclusion

1. REST issues

We will observe a simple API of a blog web application. The Spotlight will be turned to post resource.

GET /posts

[
   {
      id
      title
      content
      category
   }
]

We want to display a list of posts with their titles on our website. And this is what we get in response:

{
     "id": 2,
     "title": "World Cup",
     "content": "Very long and interesting content",
     "category": "SPORT"
}

The response we received contains data which are important for us, however, it also contains superfluous information.
Content field can be potentially heavy and definitely is not in our goal. This situation is called OVER-fetching. We just wanted a list of posts titles, but we received a lot of other additional data.

Now we will observe the opposite case. We add an author id to our post resource. We want to fetch all posts with their authors’ names.

GET /posts

[
  {
     id
     title
     authorId
     category
  }
]

And in response we receive:

{
     "id": 2,
     "title": "World Cup",
     "category": "SPORT",
     “authorId” : 1
}

In this scenario, with the first request, we receive all posts but only with author id, not author name. The second request is needed to fetch the author’s name. This problem can be more complex and to obtain all needed information more requests could be required. This issue is called UNDER-fetching.

Both of these issues can be solved with projections usage. We can use the following projection to get Author data for our last example only in one request:

GET /posts/1?projection=with-authors

This is a solution to our problem, but not a widely adopted standard. What’s more – our API is still not flexible.
We only react to our client’s needs, because we would need to implement a new projection every time a new requirement appears. It makes collaboration harder.

2. GraphQL – REST alternative

These issues are not really problematic in most of the projects. It starts to be troublesome when we scale it and performance is a key aspect of our project. In such a situation, every optimization is at the premium.

Facebook came into the same conclusion and in 2015 released a new data query and manipulation language called GraphQL. It was designed as an alternative to REST and any other web service architecture. It was a really successful project, thus other big players in the market like GitHub, Twitter or Pinterest adopted it.

GraphQL has an implementation in all major technologies. Implementations in javascript ecosystem (react +vulcanjs, node.js) seem the most mature, however, in this tutorial we will focus on how it works in Java environment.

2.1 Writing schema

GraphQL conveniently employs contract-first design approach. It has a strongly typed schema and all operations supported by the API are defined there. We could call it a heart of GraphQL. It can be defined anywhere under a resource directory in a project with .graphqls extension.

Moreover, it can split between many files, so we can build a structure similar to our java code. Main entities used in schema are types and queries, used to fetch data from our application server.

Types for our Blog application can be defined as follows:

type BlogPost {
    id: ID !
    title: String !
    content: String !
    category: Category
    author: Author !
}

type Author {
    id: ID !
    name: String !
}

enum Category {
    SPORT
    TRAVEL
}

It’s similar to JSON syntax. The left side contains names of properties while on the right, you specify their types. We use here predefined ID type which is a scalar type. It represents a concrete data and is a leaf in the query tree. More about scalars can be found here. Title and content are also properties of scalar type.

Category and Author are complex types and they need to be defined in schema as well. Exclamation mark denotes that a field is required. To fetch data described by these types we need to have at least one Query defined. Queries are entry points of every GraphQL tree. Only one type Query can exists in schema, but it may contain many methods.

type Query {
    allBlogPosts: [BlogPost]
}

This schema will allow us to do the following query:

{
   allBlogPosts
   {
       title
       category
       author{
         name
       }
   }
}

Queries are executed against graphQL server as a GET request to /graphql endpoint, where the server is exposed. Nevertheless, it is worth to notice that GraphQL is protocol agnostic and can be applied to various data transport solutions.

Writing query is really simple. We need to specify existing query name and then, in curly braces, type output parameters which we want to fetch with this request.

In response, we will receive a message in JSON format which reflects output data from query definition.

"data": {
    "allBlogPosts": [
      {
        "title": "World Cup",
        "category": "SPORT",
        "author": {
          "name": "John"
        }
      },
      {
        "title": "Great adventure,
        "category": TRAVEL,
        "author": {
          "name": "John"
        }
      }
    ]
}

We excluded id and content fields from the BlogPost type. Additionally, we defined that we want to have author name. We don’t need to define additional endpoints or create projections. All this happens in graphQL server. It gives our API customers a huge flexibility. Now it’s possible for API’s clients to fetch data in any format they want and all it happens in a single round trip to the server.

2.2 Implementation in Spring Boot

Although the schema file is fundamental, graphQL can’t work without a proper configuration. In the few next steps of this tutorial, we will see what is required to set up the GraphQL server in Spring Boot application.

Like everything in Spring Boot, it is pretty straightforward. Adding these few dependencies to our pom.xml file is enough to start GraphQL server.

<dependency>  
   <groupId>com.graphql-java</groupId>  
   <artifactId>graphql-java-tools</artifactId>  
   <version>4.3.0</version>  
</dependency>
<dependency>  
   <groupId>com.graphql-java</groupId>  
   <artifactId>graphql-spring-boot-starter</artifactId>  
   <version>4.0.0</version>
</dependency>
<dependency>  
   <groupId>com.graphql-java</groupId>  
   <artifactId>graphiql-spring-boot-starter</artifactId>  
   <version>4.0.0</version>  
</dependency>

The next step is adding proper resolvers. Resolvers are classes which handle queries and mutations on application’s data. Query, which is an entry point to our graph, requires to have an implementation in a bean in Spring context. This bean needs to implement GraphQLQueryResolver interface. It is a markup interface and only public methods which should be placed in that bean are defined in schema.

@Service
@AllArgsConstructor
public class Query implements GraphQLQueryResolver {

    private final BlogPostRepository blogPostRepository;

    public List allBlogPosts() {
        return StreamSupport.stream(blogPostRepository.findAll().spliterator(), false)
                .collect(Collectors.toList());
    }
}

Name of the method should be equal to the method in type Query from schema. It can return simple types, which will be mapped automatically to scalar values from schema, or it can return more complex types. Complex return types should be also represented in Java. It can be (but doesn’t have to) a simple POJO and should have fields with the same names as in the schema. All of these fields should also have public getters.

@Entity
@Data
public class BlogPost {

@Id
@GeneratedValue
    private Long id;
    private String title;
    private String content;
    private Category category;
    private String authorId;
}

Most of these fields are easy to load, but what about authorId field? It can be loaded as it but then we won’t be able to fetch author’s name or address. This can be handled with field resolvers which are Spring beans implementing GraphQLResolver interface.

@Service
@AllArgsConstructor
public class BlogPostResolver implements GraphQLResolver {

    private final AuthorRepository authorRepository;

    public Author getAuthor(BlogPost blogPost) {
        return authorRepository.findById(blogPost.getAuthorId())
                .orElseThrow(EntityNotFoundException::new);
    }
}

In this class, we can define a resolver method for all complex types which requires some additional operations or computing. The function which fetches type should have name equals to one of the following:

typeName
get<typeName>
is<typeName>

2.3 How we change data – mutations

Until now we were talking only about querying our API. In a real-world scenario, we also create and change data. GraphQL delivers a tool addressing this need called Mutations.

Mutation is a type in graphQL schema similar to Query type. There can be only one Mutation type in a schema and all mutations begin here. With this type, client is informed that an operation will change a data.

type Mutation {
    createPost(title: String !, content: String !, category: Category, authorId: ID): BlogPost
}

It has the same syntax as Query type, however, there is one significant difference. We can’t use complex query types as mutation argument. For mutation complex type are even defined differently in schema – type keyword is replaced with input.

input Author {
    name: String !
}

Mutation, like a Query, requires a special bean in Spring context which should implement GraphQLMutationResolver interface.

@Service
@AllArgsConstructor
public class Mutation implements GraphQLMutationResolver {

    private final BlogPostRepository blogPostRepository;
    private final AuthorRepository authorRepository;

    @Transactional
    public BlogPost createPost(String title, String content, Category category, Long authorId) {

        BlogPost blogPost = new BlogPost();
        blogPost.setTitle(title);
        blogPost.setContent(content);
        blogPost.setCategory(category);
        blogPost.setAuthor(authorRepository.findById(authorId).orElseThrow(EntityNotFoundException::new));
        return blogPostRepository.save(blogPost);
    }
}

2.4 Let’s annotate it – GraphQL SPQR

The approach presented above has one significant downside. We have two separate sources of truth: schema and application model. Every change to schema needs to be applied to model or resolver as well. The same is in reverse. It introduces a potential point of failure during API development.

Definitely, this is not a way to go. A solution to this is graphQL SPQR library. This project eases the whole workflow with graphQL by adding a lot of automatization. Thanks to this library we even don’t care about schema creation. It will be created for us automatically basing on our code. Let’s see how it look in practice.

First of all, we need to add the following dependency to our pom.xml file:

<dependency>  
   <groupId>io.leangen.graphql</groupId>  
   <artifactId>spqr</artifactId>  
   <version>0.9.6</version>  
</dependency>  

And remove:

<dependency>  
   <groupId>com.graphql-java</groupId>  
   <artifactId>graphql-java-tools</artifactId>  
   <version>4.3.0</version>  
</dependency>  

Now, we need to configure SPQR with graphQL Spring Boot Starter. In @Configuration class GraphQL bean will be modified to be used with our library and then the bean will act as GraphQL query resolver in prepared server endpoint.

@Configuration
public class DemoConfiguration {

    @Bean
    public GraphQL graphQL(BlogPostService blogPostService) {

        GraphQLSchema schema = new GraphQLSchemaGenerator()
                .withResolverBuilders(
                        new AnnotatedResolverBuilder(),
                        new PublicResolverBuilder("com.arturskrzydlo.graphqldemo"))
                .withOperationsFromSingleton(blogPostService)
                .withValueMapperFactory(new JacksonValueMapperFactory())
                .generate();
        return GraphQL.newGraphQL(schema)
                .queryExecutionStrategy(new BatchedExecutionStrategy())
                .instrumentation(new ChainedInstrumentation(Arrays.asList(
                        new MaxQueryComplexityInstrumentation(200),
                        new MaxQueryDepthInstrumentation(20)
                )))
                .build();
    }
}

@RestController
public class GraphQLController {

    @Autowired
    private GraphQL graphQL;

    @PostMapping(value = "/graphql", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public ExecutionResult execute(@RequestBody Map<String, Object> request) {
        return graphQL.execute(ExecutionInput.newExecutionInput()
                .query((String) request.get("query"))
                .operationName((String) request.get("operationName"))
                .build());
    }
}

With this configuration, no special work is needed to have a working application. We don’t even need resolvers anymore. Just a @Service bean will be enough. SPQR library will look for all public methods and their return types in the service and will create schema on the fly.

@Service
@AllArgsConstructor
public class BlogPostService {

    private final BlogPostRepository blogPostRepository;

    public List allBlogPosts() {

        return StreamSupport.stream(blogPostRepository.findAll().spliterator(), false)
                .collect(Collectors.toList());
    }
    
}

There is a place for customization here, so we can change method argument names to be more friendly in our API. It is of course just a simple example but shows how the library can be useful. We can utilize code patterns which we have used and everything is made under the hood. Code first approach is also a benefit.

The project is still evolving but it’s already functional. Currently, project authors working on adding this library to Spring Boot Starter.

2.5 Testing queries and mutations

GraphQL comes also with a really useful tool called graphiql. This is a UI which allows to communicate with graphQL server and execute queries on it.

It’s really handy, we can test our API without additional effort. The tool can be downloaded as a separate app or can be included to projects dependencies (as in this tutorial).

<dependency>  
   <groupId>com.graphql-java</groupId>  
   <artifactId>graphiql-spring-boot-starter</artifactId>  
   <version>4.0.0</version>  
</dependency>  

With this configuration graphiql should be available at :/graphiql after Spring Boot server startup. It works only when graphQL server is configured to be available at default /graphql endpoint.

garphiQL

In console available at the left tab user can type query (or mutation) and the result will be displayed on the right side tab. The console has really valuable autocomplete and syntax check features.

3. Bad parts

There is no such thing as perfect technology. All have their drawbacks and it is the same with GraphQL. Below you can find a short list of main issues, which everybody should consider, before deciding to use this technology in a project:

  • Caching – actually with GraphQL caching can be implemented only on client side and application layer (no network caching), but for a client, it’s a poor implementation. More about this here.
  • Lack of operation idempotency.
  • Security – graphQL-based applications may be prone to development implementation errors.
  • Error handling – in REST API we have status codes, but it’s not possible to have a one status code for many requests. And GraphQL runs a lot of queries in one request. There are workarounds but it’s still troublesome.
  • Hypermedia – with REST HATEOAS we can represent state transitions by providing links to them. With GraphQL we have a lot flexibility but client’s don’t know without documentation how to move from one state to another.

4. Conclusion

The main issue which GraphQL is solving the minimization of requests to the API server from web applications. It has its drawbacks described in the previous point.

However, it also has a lot of other areas where it shines, just to mention schema stitching, which allows combining multiple APIs into one or event-based subscription mechanism (allows clients to be notified about changes on the server).

GraphQL has a rich open-source ecosystem and a booming community. Java solutions are evolving and are used on production systems. Definitely, because of its drawbacks, it won’t fit every project, but it does a great job in its own area.

Last but not least and worth to remember: it’s only a tool in developer’s hands. If you are not able to provide good REST API, GraphQL won’t help you in that matter.



LEAVE A COMMENT