GraphQL
Estimated time to read: 4 minutes
Overview¶
GraphQL is a query language for APIs or a syntax that describes how to ask an API for data.
Whilst REST is a popular way to expose data, the response returned from a REST API is rigid and returns all of the data points as designed by the developer, whether required or not. GraphQL offers greater flexibility in the response returned, over REST.
The calling client can specify exactly what data it requires and also allows for the aggregation of multiple sources on the backend, allowing the client to make one call to get all of the data it requires
As a query language, GraphQL provides a flexibility that normal APIs or web services do not. Unlike SOAP or REST APIs, GraphQL gives you the ability to specify, in the API request, specifically what data is needed and returns exactly that back.
Overall, GraphQL offers greater efficiency and flexibility.
Sample Query¶
Each query has a speicifc object that it returns. Based on the returned object, fields can be added or removed to match the exact data needed to fit a specific use case.
Example One¶
Query¶
Reponse¶
Example Two¶
Query¶
Response¶
{
"data":
{
"findAllApplications":
[
{ "id": "1", "owner": "Kesha"},
{ "id": "2", "owner": "Jane"}
]
}
}
Dependencies¶
To include GraphQL in a project, the following dependencies are required:
// ...
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>X.y.z</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>X.y.z</version>
</dependency>
//...
graphql-spring-boot-starter
adds and automatically configures a GraphQL servlet to the project that can be accessed at /graphql
. It also adds a GraphQL schema library to parse all schema filers found on the classpath and an endpoint that can access POST requests.
graphql-java-tools
is a helper library to parse the GraphQL schema. graphql-java-tools
parses schemas ending in .graphqls
.
Schemas¶
The GraphQL schema defines the data points offered via the API.
The schema contains the data types and relationships between them and the set of operations available, such as queries for retrieving data and mutations for creating, updating, reading and deleting (CRUD) data.
Example Schema¶
In the example below;
Application
type is the main type.- Note that each field is Typed
- Each field suffixed with
!
indicates that it is required Query
type lists the available query operations- Types surrounded with
[ ]
indicate an array Mutation
type lists the available mutation operations.
Note
There can only be one root Query and one Mutation type.
Note
Each file contains all of the query and mutation operations for the given type.
type Application {
id: ID!
name: String!
owner: String!
description: String!
}
type Query {
findAllApplications: [Application]!
countApplications: Long!
}
type Mutation {
newApplication(name: String!, owner: String!, description: String!) : Application!
deleteApplication(id: ID!) : Boolean
updateApplicationOwner(newOwner: String!, id:ID!) : Application!
}
Query Operations¶
A query resolver is used to access the repository which will then query the database.
The query resolver allows Spring to automatically detect and call the correct method in response to a GraphQL queries defined in the schema.
Example¶
Notice, in the example below, how there are methods that line up to those expressed in the GraphQL schema for the object.
// ...
@Component
public class Query implements GraphQLQueryResolver {
private ApplicationRepository applicationRepository;
public Query(ApplicationRepository applicationRepository) {
this.applicationRepository = applicationRepository;
}
public Iterable<Application> findAllApplications() {
return applicationRepository.findAll();
}
public long countApplications() {
return applicationRepository.count();
}
}
// ...
Mutations¶
GraphQL has the ability to update the data stored within the database through the use of Mutations. Mutations such as creating, updating or deleting will change the data, unlike a query.
Mutations are defined in the Java code by defining a class that implements GraphQLMutationResolver
. A mutation resolver allows Spring to automatically detect and call the right method in response to one of the GraphQL mutations declared inside of the schema.
Example¶
For example, using the schema declared above, there are three mutations available. These are:
- newApplication
- deleteApplication
- updateApplication
// ...
@Component
public class Mutation implements GraphQLMutationResolver {
private ApplicationRespository applicationRepository;
public Mutation(ApplicationRespository applicationRepository) {
this.applicationRepository = applicationRepository;
}
public Application newApplication(String name, String Owner, String description) { // newApplication method
Application app = new Application(name, owner, description); // Creates a new application with the values passed in
applicationRepository.save(app); // Calls the save method to save the new app to the applicationRepository
return app;
}
public boolean deleteApplication(Long id) { // deleteApplication method
applicationRepository.deleteById(Id); // Calls deleteById and removes the application from the applicationRepository
return true;
}
public Application updateApplicationOwner(String newOwner, Long id) { // updateApplication method
Optional<Application> optionalApplication = applicationRepository.findById(Id); // Finds the applications within the applicationRepository by ID
if(optionalApplication.isPresent()) { // If optionalApplication exists
Application application = optionalApplication.get(): // Get details of the optionalApplication
applicaton.setOwner(newOwner); // Set the owner based on the parameter passed in
applicationRepository.save(application); // Save the update application to the applicationRepository
return application;
} else {
throw new ApplicationNotFoundException("Application Not Found", id); // Else, throw an exception
}
}
}
// ...
Exceptions¶
Similar to how errors are handled within a RESTful API, by throwing an ApplicationNotFound exception, the same model can be applied to GraphQL.
In the example below, if a client requests an application that is not available within the database, an exception is thrown.
Note that the custom exception ApplicationNotFoundException
extends RuntimeException
and implements GraphQLError
.
GraphQLError
provides the extensions
field which is used to pass additional data to the error object set to the client.
// ...
public class ApplicationNotFoundException extends RuntimeException implements GraphQLError {
private Map<String, Object> extensions = new HashMap<>();
public ApplicationNoutFoundException(String message, Long invalidApplicationId) {
super(message);
extensions.put("invalidApplicationId", invalidApplicationId);
}
@Override
public List<SourceLocation> getLocations() {
return null;
}
}
// ...