Build GraphQL API with Spring Boot Build GraphQL API with Spring Boot

Page content

In this article, we’ll learn step by step, how to build GraphQL API with Spring Boot.

GraphQL

GraphQL is a query language to fetch data from APIs. GraphQL is developed by Facebook as an alternate of REST APIs. GraphQL solves some of the issues which arise when REST APIs evolves and scale.

REST vs GraphQL
  1. Any REST endpoint generally returns a response in JSON in specific format. You either get a full JSON response or nothing at all. GraphQL provides ability to query API and get exactly what you need, nothing more and nothing less.
  2. Any REST endpoint generally returns a response of specific Domain. Sometime clients need to call multiple REST endpoint to collect data. GraphQL provides ability to collect data in a single query.
  3. You perform CRUD operations in REST using different HTTP verbs (GET, POST, PUT, DELETE etc.) whereas CRUD operations in GraphQL is performed using Query (or Mutation). Clients query the GraphQL API by making an HTTP POST request with Query (or Mutation) as Request Body.

GraphQL Terminology

Let’s have a look at GraphQL’s basic terminology.

  • Schema: is a GraphQL schema which includes Query and Mutation
  • Query: is a read operation requested to a GraphQL Server.
  • Mutation: is a create, update and delete operations requested to GraphQL Server.
  • Resolver: is responsible for mapping the Query (or Mutation) operation to backend service responsible to handle the request.
  • Fields: are the properties of the GraphQL objects for e.g. User, Post and Comment etc.
  • Scalar: is the type of the field for e.g. Int, Float, String, Boolean, ID. ID represents unique identifier, serialized as String.
  • Enum: is a special kind of Scalar that is restricted to a particular set of allowed values.
  • Interface: is an abstract type that includes a certain set of fields that a type must include to implement the interface.
  • Input: is a GraphQL object passed, which is passed in Mutation operation such as create and update.

GraphQL Schema

It’s time to define some GraphQL schema where you can relate to some of the GraphQL Terminology:-

schema.graphqls
# Query for read operations
type Query {
	users: [User],  # [User] means List of User
	userById(id:ID): User	
}

# Mutation for create, update and delete operations
type Mutation {
	createUser(input:UserInput): User,
	updateUser(input:UserInput): User,
	deleteUser(id:ID): Boolean
}

# Interface
interface Person {
  id: ID!		   # id of type ID is Non-Null
  name: String!    # name of type String is Non-Null
}

# User Object
type User implements Person {
	id: ID!,
	name: String!,
	age: Int,
	height: Float,
	gender: Gender
}

## Enum Scalar
enum Gender {
  MALE
  FEMALE
}

# UserInput to use in Mutation
input UserInput {
	name: String,
	age: Int,
	height: Float,
	gender: Gender
}

# Post Object
type Post {
    id: ID!,
    userId: String,
    title: String,
    body: String,
    comments: [Comment]  # Nested Query Object
}

# Comment Object
type Comment {
    id: ID!,
    postId: String,
    name: String,
    email: String,
    body: String,
    post: Post 	 # Nested Query Object
}

GraphQL-Java

GraphQL-Java is the Java (Server) implementation of GraphQL Specification. It provies Java library to define GraphQL Schema, Query and Mutation and resolve the using Resolver.

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

You have to write a lot of boilerplate code if you use graphql-java.

Fortunately, We have two libraries available which provides a wrapper on top of graphql-java and saves use from writing boilerplate code. They are:-

  1. GraphQL Java Kickstart
  2. Netflix DGS

GraphQL Java Kickstart

GraphQL-Java-Kiskstart provides a wrapper on top of graphql-java and has following features:-

  1. Provide comprehensive Spring boot configuration to customize GraphQL Java Server
  2. Auto-detect schema files in src/main/resources/*.*/*.graphqls directory. This is where you write GraphQL schema, queries and mutation.
  3. Concepts of Resolver. Implement GraphQLQueryResolver, GraphQLMutationResolver and GraphQLResolver<T> to specify how to fetch data for the queries, mutation and nested data respectively.
  4. Easy integration with build tools such as GraphiQL, PlayGround and Voyager by adding runtime dependency. Provide comprehensive Spring Boot Configurations to customize these tools.
  5. Easy to write integration test using GraphQLTestTemplate provided by test dependency.
  6. Excellent tutorial series by Philip Starritt which gives you quick start.

Project Setup

Requirement
  • Java 1.8 and Above
  • Spring Boot Framework > 2.x.x (web)
Gradle
repositories {
    mavenCentral()
}

dependencies {
  // to turn spring boot application into GraphQL server
  implementation 'com.graphql-java-kickstart:graphql-spring-boot-starter:11.0.0'
  
  // to embed Altair tool for schema introspection and query debugging
  runtimeOnly 'com.graphql-java-kickstart:altair-spring-boot-starter:11.0.0'

  // to embed GraphiQL tool for schema introspection and query debugging
  runtimeOnly 'com.graphql-java-kickstart:graphiql-spring-boot-starter:11.0.0'

  // to embed GraphQL Playground tool for schema introspection and query debugging
  runtimeOnly 'com.graphql-java-kickstart:playground-spring-boot-starter:11.0.0'

  // to embed Voyager tool for visually explore GraphQL APIs as an interactive graph 
  runtimeOnly 'com.graphql-java-kickstart:voyager-spring-boot-starter:11.0.0'
  
  // testing facilities
  testImplementation 'com.graphql-java-kickstart:graphql-spring-boot-starter-test:11.0.0'
}
Maven
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>11.0.0</version>
</dependency>

<!-- to embed Altair tool -->
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>altair-spring-boot-starter</artifactId>
    <version>11.0.0</version>
    <scope>runtime</scope>
</dependency>

<!-- to embed GraphiQL tool -->
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>11.0.0</version>
    <scope>runtime</scope>
</dependency>

<!-- to embed GraphQL Playground tool -->
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>playground-spring-boot-starter</artifactId>
    <version>11.0.0</version>
    <scope>runtime</scope>
</dependency>

<!-- to embed Voyager tool -->
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>voyager-spring-boot-starter</artifactId>
    <version>11.0.0</version>
    <scope>runtime</scope>
</dependency>

<!-- testing facilities -->
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter-test</artifactId>
    <version>11.0.0</version>
    <scope>test</scope>
</dependency>

Writing the Schema

GraphQL library auto detects the schema files with “.graphqls” extension in classpath resources and wires them to model and resolver spring beans. Things to note:-

  • We can have multiple schema files in the classpath, so we can split the schema into modules as required.
  • We must have only one root Query and one root Mutation across all the schema files. This is a limitation of the GraphQL Schema itself, and not of the Java implementation.
schema.graphqls
# Query for read operations
type Query {
	users: [User],  # [User] means List of User
	userById(id:ID): User	
}

# Mutation for create, update and delete operations
type Mutation {
	createUser(input:UserInput): User,
	updateUser(input:UserInput): User,
	deleteUser(id:ID): Boolean
}

GraphQL Query Resolver

Next, we implement GraphQLQueryResolver to resolve Queries (Read Operations). Note that the method names (users, userById) and return types (User) are same in GraphQL schema and QueryResolver class.

@Component
@RequiredArgsConstructor
public class UserQueryResolver implements GraphQLQueryResolver {

    private final UserService userService;

    List<User> users() { return userService.getAllUsers();}

    User getUserById(Long userId) { return userService.getUserById(userId);}
}

GraphQL Mutation Resolver

Next, we implement GraphQLMutationResolver to resolve Mutations (Create, Update and Delete Operations). Note the method names and return types.

@Component
@RequiredArgsConstructor
public class UserMutationResolver implements GraphQLMutationResolver {

    private final UserService userService;

    User createUser(UserInput input) { return userService.createUser(input); }

    User updateUser(UserInput input) { return userService.updateUser(input); }

    Boolean deleteUser(Long id) { return userService.deleteUser(id); }
}

GraphQL Field Resolver

Sometimes, GraphQL object can have nested GraphQL object, which result in nested queries. We implement GraphQLResolver<T> to resolve such nested Queries. For e.g. User can have multiple posts [Post]

@Component
@RequiredArgsConstructor
public class UserPostResolver implements GraphQLResolver<User> {

    private final PostService postService;

    List<Post> posts(User user) { return postService.getAllPostsByUserId(user.getId());}
}

Next Steps

This is a good starting point. Next steps are configuring tools such as Playground and Voyager, Write Test cases etc. You can download graphql-java-kickstart-example github project for more details.

Netflix DGS

Netflix-DGS is developed by Netflix on top of graphql-java and recently made it public to use. It has following features:-

  1. Do not provide Spring boot based configurations
  2. Auto-detect schema files in src/main/resources/schema/*.*/*.graphqls directory. This is where you write GraphQL schema, queries and mutation.
  3. Concepts of DataFetcher. Provide annotations @DgsComponent at class level and @DgsQuery, @DgsMutation, @DgsData at method level to specify how to fetch data for the queries, mutation and nested data respectively.
  4. Provide integration with GraphiQL
  5. Provide good support to write unit and integration test cases using DgsQueryExecutor
  6. Follow example to quick start

Project Setup

Requirement
  • Java 1.8 and Above
  • Spring Boot Framework > 2.x.x (web)
Gradle
repositories {
    mavenCentral()
}

dependencies {
    implementation "com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:latest.release"
}
Maven
<dependency>
    <groupId>com.netflix.graphql.dgs</groupId>
    <artifactId>graphql-dgs-spring-boot-starter</artifactId>
    <!-- Make sure to set the latest framework version! -->
    <version>${dgs.framework.version}</version>
</dependency>

DataFetcher

We can resolve Queries and Mutations, all in a single DataFetcher class file. We use @DgsComponent annotation at class level for auto spring bean registy and use following annotations at method level:-

  • @DgsQuery for Root Queries
  • @DgsMutation for Root Mutations, and
  • @DgsData for Nested Queries
@DgsComponent
@RequiredArgsConstructor
public class UserDataFetcher {

    private final UserService userService;
    private final PostService postService;

    @DgsQuery
    public List<User> users() { return userService.getAllUsers();}

    @DgsQuery
    public User userById(String id) { return userService.getUserById(Long.parseLong(id));}

    @DgsMutation
    public User createUser(@InputArgument("input") UserInput userInput) { return userService.createUser(userInput); }

    @DgsMutation
    public User updateUser(@InputArgument("input") UserInput userInput) { return userService.updateUser(userInput); }

    @DgsMutation
    public Boolean deleteUser(String id) { return userService.deleteUser(Long.parseLong(id)); }

    @DgsData(parentType = "User")
    public List<Post> posts(DgsDataFetchingEnvironment dfe){
        User user = dfe.getSource();
        return postService.getAllPostsByUserId(user.getId());
    }
}

Next Steps

Next steps are to try GraphiQL tool for Query introspection and debugging exposed at /grahiql, Write test cases etc. You can download netflix-dgs-example github project for more details.

Conclusion

In this tutorial, We learned about GraphQL basics, its terminology. We also compared the two GraphQL-Java wrapper libraries, GraphQL Java Kickstart and Netflix DGS and their usage.