How to start with GraphQL in Java

In this blog post, you will learn some basics about GraphQL and how to start using it in an actual project with Java language and Spring Boot technology.

What is GraphQL?

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. The main concept of GraphQL is strong typing which determines the way of communication between the client and server. GraphQL’s main feature is its flexibility, which can be determined on the basis of two phenomena that very often occurs when using REST architecture – data over and under-fetching.

Data Under-Fetching is a phenomenon that occurs when there is fewer data in response than the requester needs. This situation forces the client or other microservice to shoot the server whit another request to get the missing data.

Data Over-Fetching is a phenomenon that occurs when there is more data in response than the client or other microservice uses.

In both cases, there are performance issues that cause problems. In the first case, the client is forced to make additional HTTP requests when it should send only a single request in the ideal situation. In the second case, there is more usage of bandwidth than it could be, as the client is not using all the data that was received, which also slows down the application.

Against these problems, there is the GraphQL language, which thanks to the above-mentioned flexibility, i.e. the possibility of sending many queries within one request and the possibility of selecting the required fields within each request eliminates these two phenomena such as data under and over-fetching.

Basics of GraphQL

In GraphQL to create an API, it is necessary to create schemas that will define possible queries including what this query will return and what are the required parameters. There are two main concepts for operating on the data: Queries and Mutations.

Query in GraphQL is a substitute for HTTP GET in REST architecture and it’s used only to get data from the server.

Mutation is a substitute for HTTP POST/PUT/DELETE which is used to operate on data especially by sending new data to the server, updating already existing data, or deleting it from the server.

In each of the GraphQL operations, we have some keywords that allow defining schemas:

  • type – represent a kind of object that it’s possible to fetch from service, and what fields it has. This keyword is allowed to use only in Queries.
  • input – represents a kind of object that will make some changes in the server, allowed to use only in Mutations.
  • interface – the abstract type that includes a certain set of fields that a type must include in implementing the interface.
  • enum – enumeration types are a special kind of scalar that is restricted to a particular set of allowed values.
  • union – very similar to interfaces, but they don’t get to specify any common fields between the types.

When defining schemas there are some predefined types like in other languages: Float, String, Boolean, Int, and new type ID which is a scalar type that represents some unique identifier.

Enough theory, let’s start with some practical stuff such as integrating GraphQL with Spring Boot.

Spring Boot logo

To start it is recommended to use https://start.spring.io/ to initialize your project. There are some necessary requirements to do. The Spring Boot version must be greater or equal to 2.7.0 and at least two packages must be included: Spring Web and Spring for GraphQL.

Spring Starter for new GraphQL project
Spring Starter for the new GraphQL project

After successfully initializing your project if you are using IntelliJ it is also recommended to install a GraphQL plugin that will help with some stuff while coding.

GraphQL plugin for IntelliJ
GraphQL plugin for IntelliJ

Defining GraphQL schemas for our project

The scope of the project that we want to create for this lesson is to allow users to fetch all products and their details, and also filter products by price. There should be also some part of the functionality for admin users which includes adding, updating, and deleting products. In the first step of creating an API in the resources folder, we must create graphql folder which is the default path where schemas with .graphqls extension will be searched by the GraphQL library. Schemas could be separated into as many files as you want which is very helpful, especially in larger projects.

GparhQL schemas folder structure.
GparhQL schemas folder structure

As shown in the above image I recommend creating a main schema file called schema.graphqls in which we will define available Queries and Mutations and also create a package for each domain (in this case only products) which will be separated into two schemas – one for Mutation inputs and one for Query types.

Let’s start with defining types for Query operations in the domain of the product:

GraphQL schema for Product.
GraphQL schema for Product

As you can see the type of Product consists of a unique id, name, price, and available types which consist of enums such as sex, color, and size of the product. Here you can also see two operators – ! and [], the first one informs us about the fact that this field can’t be null, and if it is, the server will respond with an error message. The second operator is used to define lists like in most programming languages. In that schema, availableTypes can be null, but because of ! after ProductType, elements inside the list of the available types will never be null.

GraphQL Queries for Products
GraphQL Queries for Products

As we have defined our types for the Products domain there is a time to define available Query operations. It’s worth remembering that in all schema files Query type must be defined only in one place.

Our available Query operations are products which will return a not nullable list of Product types with elements that also will never be null, product which takes not null unique id as a parameter and returns Product type, and the last one productsByPrice which takes two parameters priceFrom and priceTo which can be nullable, this operation will return a list of Product type.

After creating part of our schema we can move this to our Java implementation, by converting defined schema types to Java classes. If you won’t change any field after creating objects it is recommended to use records as it takes less code.

Java class for Product.
Java Record for ProductType type.
Java enum for ColorType type.

After moving schemas to classes we can start with implementing a Controller for our products domain.

Spring Boot Controller class for GraphQL API
Spring Boot Controller class for GraphQL API

To create our Controller we need to annotate it with @Controller annotation from org.springframework.stereotype package. To handle our schema Query operations we have two possibilities. The first one is by using @SchemaMapping annotation in which we don’t have to be consistent in naming of methods and their corresponding schema operation, but it is necessary to pass at least two arguments to this annotation: typeName – in our case Query tells what kind of operation it is – and the value which should be the name of the schema operation. The second option is to use @QueryMapping annotation which under the hood is the former one but it binds schema operation with this method by its name, so it is necessary in that case to be consistent in the naming of methods and schema operations.

To take request parameters there is also @Argument annotation that maps fields into required types by name of the parameter.

Let’s move to define Mutations in the domain of the products.

GraphQL mutations for Product
GraphQL mutations types for Product

As mentioned above to define mutation types it is necessary to use the input keyword, types cannot be used here, but enums can be used between these two operations.

GraphQL mutation for Product

As we have defined our inputs for the Products domain there is a time to define available Mutation operations. It’s worth remembering that in all schema files Mutation, like Query type must be defined only in one place and all Mutation operations should return something.

Our available Mutation type operations are createProducts which takes as parameter CreateProductMutation input, updateProduct which takes as parameter UpdateProductMutation, and deleteProduct which takes as a parameter the unique ID of the product to be deleted. All of these mutations return the unique ID of the product on which they operate.

Again we moved schema inputs to the Java records, as mutations will not be changed. After that, we can add the handling of defined mutations in our ProductsController.

Controller methods for handling mutation requests

To handle our Mutation schema operations there is a specified annotation @MutatuionMapping which QueryMapping is under the hood @SchemaMapping. This annotation also requires to be consistent with operations defined inside schemas.

Testing API with GraphiQL

To test created API we can use Postman or another tool but the GraphQL library for Spring Boot has a built-in extension called GraphiQL to make it easier for developers.

To enable this extension it is necessary to add an entry in the application.properties file which is:

spring.graphql.graphiql.enabled=true
server.port=9000

After adding this entry and running our application the GraphiQL should be available at http://localhost:9000/graphiql?path=/graphql.

GraphiQL user interface
GraphiQL user interface

GraphiQL is the GraphQL integrated development environment that allows sending requests to the API. It contains built-in documentation which can be displayed by clicking the button on the left side of the window. Documentation is available by prefetching defined schemas, which allows for checking all possible operations, types and their explanations. It also provides suggestions when writing GraphQL requests, or the history of our requests.

By default, types defined in schemas have no description, but by adding # description above the type or field, we can add it to the documentation in GraphiQL, which can be useful in more complex types or business aspects.

GraphQL type with documentation for GraphiQL extension
GraphQL type documentation shown in the GraphiQL user interface

Let’s make our first Query request with GraphQL:

To send a Query to the server it should be wrapped inside the query keyword, after that by clicking ctrl + space, GraphiQL will suggest available operations to perform. As you can see in the products operation, we specify that we want only IDs and sizes of available types in the response; that is exactly what the server returns.

GraphQL request and response
GraphQL request and response

Here we can see the mentioned above flexibility of GraphiQL. The developer fully controls which fields will appear in the response, by adding their names based on the defined schemas.

Example of multiple Queries send with one request
Example of multiple Queries sent with one request

In this example, we are executing three operations with one query, which results in one response with all required data – details of products with id 5, products with a price between 10 and 30, and also all products.

Let’s move to our first mutation request:

Example of mutation request
Example of mutation request

As it was for Queries, to specify our first Mutation all requests should be contained in the mutation keyword. As you can see in this example we are using the variables field, which in our situation is not relevant, but in real applications, it’s where actual data will be – we shouldn’t manipulate Query directly but only by the variables passed to it. In server response, we got the id of the created product so we can check if everything was saved correctly by fetching the product by its id:

As you can see the product was saved correctly with all data passed as variables. Now we can try to update our product:

Mutation request to update a Product

In this situation, we are also using variables to have a more generic way of sending update mutation requests. Lets again fetch our product to see that everything was updated as expected:

Query to get Product by ID

As you can see on the right side, the product was updated successfully. At the end of our journey we can try to delete a product that we created:

Mutation for deleting Product

Let’s check what products are left in the system and ensure that the product with id 6 was removed:

GraphQL Query to fetch all Products.

As you can see there is no product with id 6, so we can say that product was deleted successfully.

Summary

GraphQL is growing rapidly and it is used in many production applications by companies of any size. It could become a ubiquitous technology for API in the upcoming years, but of course, it has good and bad sides. GraphQL is very flexible and allows to prevent two common phenomena observed in REST architecture such as data over and under fetching, by allowing to request only required fields and perform more than one query or mutation at once. One of the main problems of GraphQL is the “N+1 Problem” which occurs when GraphQL resolvers issue one query to the underlying data source along with N number of subsequent queries to fetch.

Leave a Reply

Your email address will not be published. Required fields are marked *