Skip to content

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

{
    findAllApplications
    {
        id
    }
}

Reponse

{
    "data":
    {
        "findAllApplications":
        [
            { "id": "1"},
            { "id": "2"}
        ]
    }
}

Example Two

Query

{
    findAllApplications
    {
        id
        owner
    }
}

Response

{
    "data":
    {
        "findAllApplications":
        [
            { "id": "1", "owner": "Kesha"},
            { "id": "2", "owner": "Jane"}
        ]
    }
}

Dependencies

To include GraphQL in a project, the following dependencies are required:

pom.xml
// ...
<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.

Query.java
// ...

@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
Mutation.java
// ...

@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.

ApplicationNotFound.java
// ...
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;
    }
}

// ...