Introduction to Spring Boot GraphQL

In this article, we are going to discuss the Spring Boot GraphQL.We will have a look at the basics and how to use it with Spring Boot.

 

1. What is GraphQL

GraphQL is an open-source data query language with a new API standard similar to REST, created by Facebook. In simple words, it is Graph based query language for the data returned by your API, which can be used to select a filter or modify the data returned. It can be implemented over HTTP, TCP or any other transport protocol.

It enables declarative data fetching, meaning the client can fetch only the data it needs, thereby avoiding over-fetching or under-fetching data. GraphQL is an alternative to REST. In this article, we will dive into the basics of GraphQL and demonstrate the development of a custom GraphQL backend with Spring Boot.

 

2. Maven Configurations

To build a Spring Boot GraphQL application,  we need to add the following dependencies:

  • graphql-spring-boot-starter – which includes graphql-java: The Spring Boot GraphQL Starter(graphql-spring-boot-starter) gives us an easy way to make a GraphQL server running and available in a very short time. graphql-java is the GraphQL server implementation itself.
  • Graphql-java-tools – To make dynamic resolver wiring easy, we also need to use graphql-java-tools. This is inspired by graphql-tools.

Here is our pom.xml:

<dependency>
	<groupId>com.graphql-java</groupId>
	<artifactId>graphql-spring-boot-starter</artifactId>
	<version>5.0.2</version>
</dependency>
<dependency>
	<groupId>com.graphql-java</groupId>
	<artifactId>graphql-java-tools</artifactId>
	<version>3.2.0</version>
</dependency>

This will expose the GraphQL Service on the /graphql endpoint of our application, by default. This endpoint is customizable based on the application’s needs.GraphQL has a cool tool called GraphiQL. GaphiQL is a User Interface which is able to communicate with any GraphQL Server and is able to parse and execute queries. To enable GraphiQL in our application,  add the GraphiQL Spring Boot Starter dependency in the pom.xml:

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

This will works only if our GraphQL API is hosted on the default endpoint of /graphql.

 

3. GraphQL API Schema

Every GraphQL ApI has a schema that defines all the functionality available at the endpoint. Below, for example, is the schema for “userepo” API we want to provide.

type Query {
	users (first: Int=0, last: Int=0): [User]
	user(id:ID!): [User]
	repos (userId: String): [Repo]
}

type Mutation {
	createUser(login: String!, name: String!): User
	createRepo(userId: String, name: String!, description: String): Repo
}

type User {
	login: String!
	id: String!
	name: String!
	repos: [Repo!]
}

type Repo {
	name: String!
	description: String
	url: String!
	id: String!
}

This schema defines the following types:

  1. Query:  The main query type – contains queries for a single user, all users, and all repositories. This is the only mandatory type in the schema.
  2. Mutation: Lists all methods available to change data stored on the server. Has two methods one for creating a user, another for creating a repo for the user.
  3. User and Repo contain definitions of type user and repo.

 

4. Project Setup

Create a Spring Boot project using your favorite IDE include web, JPA, H2 dependencies to enable embedded web server and storage (for persistence). Here, we have used the H2 in-memory database. To demonstrate the GraphQL functionality we will start with a basic spring boot application with a database backend and a service to create, modify and delete data. If you’d like to download this initial app and follow along, head to this github repo. Our app has 4 classes namely:

  1. User.java a model class with user id, name and login fields along with getters/ setters.
  2. UserRepository implements JpaRepository for User objects on top of the H2 in-memory database.
  3. UserService is an injectable service that provides methods to create, modify and delete users.
  4. UserepoqlApplication class is our Spring boot application which has an init method to initialize the database.

 

5. Implementing Root Query

First up, let’s modify the pom.xml to add dependencies for graphql-spring-boot-starter, graphql-java-tools, and graphiql. Once this is setup we can start creating our schema file and the implementation for the API. Create a new file called userepoql.graphqls insrc/main/resources/graphql directory with below contents:

type User {
	login: String!,
	id: ID!,
	name: String
}

type Query {
	users: [User]
}

This is a subset of our intended schema with only one query that returns all the users. Implementation of the root query are beans defined in spring context that implement the GraphQLQueryResolver interface and have method name matching a field in the root query. Multiple beans can be used to implement a single root query. Let’s add a Query class that does exactly this:

package com.javadevjournal.userepoql;

import java.util.List;
import org.springframework.stereotype.Component;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;

@Component
public class Query implements GraphQLQueryResolver {
 private UserRepository store;

 public Query(UserRepository store) {
  this.store = store;
 }

 public List < User > getUsers() {
  return this.store.findAll();
 }
}

That’s it! Our GraphQL end-point is ready. Run the application and visit http://localhost:8080 with your browser to see GraphiQL in action. On the right side, you can find the schema displayed showing the semantics of the query we just implemented! Here’s a screenshot.

graphQL

 

6. Mutation – Change Data

That was easy, wasn’t it? Now let’s see how we can modify or write data to the server. Although technically there is nothing preventing a Query to modify data on the server, it is standard practice to use Mutations for making changes. Let’s add a mutation to our schema.

type Mutation {
	createUser(login: String!, name: String!): User
}

The Mutation returns the Id of the user created. We will implement this by creating a new class that implements GraphQLMutationResolver, which will internally use the UserService to create a new User. Source code for this class is below.

package com.javadevjournal.userepoql;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.coxautodev.graphql.tools.GraphQLMutationResolver;

@Component
public class Mutation implements GraphQLMutationResolver {

 @Autowired
 private UserService userService;

 public User createUser(String login, String name) {
  return this.userService.newUser(login, name);
 }
}

Like the Query class, this class also implements a method with the type signature matching the mutation. Other than the super-class being different the other rules are same for both query and mutation implementations – both should be discoverable in the application context, should have method signatures that match the schema methods and method names matching schema names. For more details please refer to the Graphql-java documentation.

Also, notice how some fields in the schema have a trailing exclamation mark (!). This indicates that these are mandatory or “non-nullable” values that must be specified in the request or returned in the response. Those without a trailing “!” are optional/ nullable. Open graphiql and take the mutation for a spin!

 

7. Add Filtering Support

Next, let’s extend the root query by adding support for getting a subset of users. We modify our users query to accept first and last parameters – when these are specified the API shall return all users with Ids between the first and last (both inclusive). Plus we add another query to return details for one user (query by ID). Here’s the modified part of the schema:

type Query {
	users(first:ID, last: ID): [User]
	user(id: ID!): User
}

Next, we modify the UserService to add support for filtering and getting individual users:

package com.javadevjournal.userepoql;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class UserService {
 private UserRepository store;

 public UserService(UserRepository db) {
  this.store = db;
 }

 public List < User > getAllUsers() {
  return this.store.findAll();
 }

 public Optional < User > getUser(int id) {
  return this.store.findById(id);
 }

 public List < User > getUsers(int first, int last) {
  if ((last == 0) || (last < first)) {
   // Ignore last if invalid value was specified
   last = (int) this.store.count();
  }
  return this.store.findAllById(
   IntStream.rangeClosed(first, last)
   .boxed()
   .collect(Collectors.toList())
  );
 }

 public User newUser(String login, String name) {
  User u = new User();
  u.setLogin(login);
  u.setName(name);
  return this.store.save(u);
 }

 public User saveUser(User user) {
  return this.store.save(user);
 }

 public void deleteUser(int id) {
  this.store.deleteById(id);
 }
}

Finally, we update the Query class to use implement the queries using UserService. Note that the User class has id field of type int and the rest of the implementation including UserService and Query class returns this as an int. The Graphl Server implemented by graphql-java library automatically converts this into the ID scalar type.

package com.javadevjournal.userepoql;

import java.util.List;
import java.util.Optional;

import org.springframework.stereotype.Component;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;

@Component
public class Query implements GraphQLQueryResolver {
 private UserService userService;

 public Query(UserService userService) {
  this.userService = userService;
 }

 public List < User > getUsers(int first, int last) {
  System.out.println("Query: [" + first + "] to [" + last + "]");
  if ((first == 0) && (last == 0)) {
   return this.userService.getAllUsers();
  } else {
   return this.userService.getUsers(first, last);
  }
 }

 public Optional < User > getUser(int id) {
  return this.userService.getUser(id);
 }
}

The getUsers() method checks for the parameter values of first and last and based on that calls the getAllUsers() method if filtering is not required. If filtering is required then it invokes the newly-created getUsers() method in UserService which returns the subset of users with Ids in the given range. We are taking advantage of the Optional class introduced in Java 8 to wrap return a nullable User object.

GraphQL FIlter

Spring Boot GraphQL

 

Summary

In this article of  Spring Boot GraphQL, we reviewed the core concepts of GraphQL and its advantages over REST. We defined a GraphQL schema and built a back-end using Spring Boot for it. You can download and run the final code here.