Build GraphQL API with Spring Boot
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
- 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.
- 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.
- 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:-
- GraphQL Java Kickstart
- Netflix DGS
GraphQL Java Kickstart
GraphQL-Java-Kiskstart provides a wrapper on top of graphql-java and has following features:-
- Provide comprehensive Spring boot configuration to customize GraphQL Java Server
- Auto-detect schema files in
src/main/resources/*.*/*.graphqls
directory. This is where you write GraphQL schema, queries and mutation. - Concepts of Resolver. Implement
GraphQLQueryResolver
,GraphQLMutationResolver
andGraphQLResolver<T>
to specify how to fetch data for the queries, mutation and nested data respectively. - Easy integration with build tools such as GraphiQL, PlayGround and Voyager by adding runtime dependency. Provide comprehensive Spring Boot Configurations to customize these tools.
- Easy to write integration test using
GraphQLTestTemplate
provided by test dependency. - 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:-
- Do not provide Spring boot based configurations
- Auto-detect schema files in
src/main/resources/schema/*.*/*.graphqls
directory. This is where you write GraphQL schema, queries and mutation. - 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. - Provide integration with GraphiQL
- Provide good support to write unit and integration test cases using
DgsQueryExecutor
- 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.