What is GraphQL and how does it differ from REST API?
When creating REST APIs, a common issue arises where clients receive an entire data resource even though they only need a small portion of it. Sending too much data that is only minimally utilized can reduce application performance and increase bandwidth consumption.
This is where GraphQL comes to the rescue. It’s a technology created by Facebook in 2012 in response to the need for efficient data management as applications grew in complexity. By allowing clients to send queries to the server in which they precisely specify the data they need, GraphQL eliminates the problems of over-fetching and under-fetching often encountered in traditional APIs.
In contrast to REST APIs, where each endpoint corresponds to a specific request, GraphQL has a single endpoint to which the client sends a query resembling a JSON structure. In this query, the client defines the specific data its’s interested in and how it wants to receive it.
Like REST API, GraphQL is not tied to a specific database. RESTful APIs accept arguments in their endpoints, which are then delegated to repositories or services in later stages. GraphQL operates in a similar way; it doesn’t directly connect to the database but relies on other tools that handle the database connection.
Sample Data Model in GraphQL
To create data models and queries in GraphQL, we use a Domain Specific Language (DSL). As I mentioned earlier, it’s similar to JSON. At a first glance, it appears to be intuitive.
In GraphQL, data models are called types. Types define the structure of data and the relationships between them. Models determine what data can be returned in response to a client’s query. Below is an example data model.
In this case, we’re dealing with a type (object) called Movie, which has four fields. The names of these fields are defined on the left side, while the variable types are defined on the right side: ID, String, and Int are some of the scalar types in GraphQL (in addition to them, there are also Float and Boolean). The exclamation mark after ID signifies that the id field will always have a value assigned to it, and there’s no need to check for null.
The director field is an object, which, like Movie, is defined as an object in GraphQL.
It’s also possible to define other variable types, such as for dates, but it requires server-side configuration.
Operation Types in GraphQL
In GraphQL, we distinguish three types of operations:
- Query – allows reading data.
- Mutation – allows writing, updating, and deleting data.
- Subscription – similar to Query, allows reading data, but establishes long-lived connections that can update the retrieved data.
Above, we have an example of a Query operation. In this case, we see two queries: allMovies and findOneMovie. The allMovies query returns a list of objects of type movie, while the findOneMovie query takes an argument of type ID, which is required, and returns a single object of type Movie.
Both queries will have their counterparts in the application controller, and a sample request to the allMovies query might look like this:
Even though the Movie object has four defined fields, when creating a request to the application server, we can specify which fields we expect values for. In this case, we omit the id and director fields, and only expect data for title and time. This is how the server response will look in this case:
In the screenshot below, there’s an example of a Mutation operation that saves a new object of type Director to the database. When defining this operation in GraphQL, we need to pass as an argument a type other than Director because this operation requires a type dedicated to saving. For this purpose, a data model of type DirectorInput has been created, which has two fields: firstName and lastName. Both fields are strings and are mandatory (indicated by the exclamation mark at the end). The query returns objects of type Director. The parameter name is important; the query accepts createDirector, and later the same name (directorToSave) must be used when creating a query to the server. Mutation query types are defined in a separate „declaration,” and each of them should have a unique name.
An example Mutation query for the createDirector method looks as follows:
The mutation query consists of two parts: the first part where we define what we want to save in the database in this case, and the second part, which specifies the expected data that the query will return.
First, we define the query type, in this case, „mutation.” Next, we specify the method we want to use, which is „createDirector.” In the query, we pass an object that we want to save, named „directorToSave,” and it must have two fields: „firstName” and „lastName.”
In the next part of the query, we specify the data we want to receive, which includes „id” and „firstName.”
After running the query, we will receive the following data:
Our object has been successfully saved in the database with an ID of 4.
The last type of operation that we can use in GraphQL is the Subscribe operation. This operation works like a query, sending a request for data to the server, but it keeps an open connection and observes changes that occur in the data during this connection.
The defined subscription query in the GraphQL file looks as follows:
First, the query type was defined, then the method, and finally, the type of returned data, which is a list of objects of type Director.
The query to the server will be similar to a query operation, with the difference being that a different type of operation will be assigned:
In the first step, we define the query type, then we provide the query name and the expected response from the database.
The server’s response at the time of sending the query may differ from the response that will be returned as the final response before disconnecting from the server.
On the left side, demonstrated is an example of the response at the time of sending the query, and on the right, an example of the response that we receive as the last one before disconnecting:
The difference between the first and the second response from the server is evident. This is because, during the connection with the server, the data in the database was updated and sent as a new server response within the same subscription query.
Implementation of GraphQL in a Spring Boot Application
To implement GraphQL in our Spring project, we need Spring Boot version 2.7+ and Java version 11+ (In my case, I used Spring Boot 3.0 and Java 17).
The first step is to add dependencies – for the implementation shown below, we need two dependencies added to our project: Spring for GraphQL and Spring Web.
In the further development stages of our application, testing libraries will also come in handy.
In the application.properties file, we can also define the property spring.graphql.graphiql.enabled=true. Thanks to this, after launching our application, we will have access to GraphiQL via the URL http://localhost:8080/graphiql. GraphiQL is an interactive environment that allows easy testing of GraphQL queries. All the query and server response screenshots presented in this article come from GraphiQL.
Data models and operations for GraphQL should be created in the resources.graphql package, so here we create the schema.graphqls file.
In this file, we can define both our data models and operations. We create data models as shown earlier. In addition to GraphQL data models, we, of course, need entities that will correspond to models in GraphQL. Operations are grouped according to their type. All query-type operations will be defined in a single „section,” regardless of the objects they return, as shown in the screenshot below. However, it’s required that each operation of the same type has a unique name – we cannot create two query-type operations named „findOne,” where one returns an object of type Movie and the other of type Director. Attempting to start the application with such a setup will result in an error.
In this case, we have defined four queries that retrieve two different types of objects.
The next step is to customize the controller of our application. We should assign appropriate annotations from the GraphQL library to our endpoints.
The @SchemaMapping annotation takes two arguments – typeName and value. In typeName, we define the operation type (query, mutation, or subscription), while the value field points to the name of our operation. However, we can omit the value parameter if the method in our controller has the same name as the query name in our graphqls file.
In the case below, the findAll() method will be associated with a query operation named allMovies.
In addition to the @SchemaMapping annotation, which takes two parameters, we can choose annotations dedicated to operation types that do not have to take any arguments. These annotations are @QueryMapping, @MutationMapping, and @SubscriptionMapping. If we want to use these annotations without additional parameters, the method in the controller and the operation in GraphQL should have the same names.
In our example, we will use the @QueryMapping annotation. The method in the controller is named findOneMovie, just like the name of the query operation in the graphqls file. Additionally, the operation defined in the graphqls file requires passing an argument of type ID each time. Therefore, in the method in the controller, when passing an Integer variable, we used the @Argument annotation. This annotation assigns the named GraphQL argument from the query to the method’s parameter. Importantly, the method parameter should have the same name as the argument in the query. If the names differ, we can assign the argument using the argument name in the annotation, where we specify the name of the GraphQL query argument.
To ensure that our application correctly returns a response for subscription-type queries, we need to add the dependency „spring-boot-starter-websocket” to the pom.xml file. This is because GraphQL subscriptions are a mechanism that enables streaming real-time data to clients. By default, GraphQL uses the HTTP protocol, which means that queries are one-time and do not allow for active streaming responses to the server. WebSocket is a protocol that enables bidirectional communication between the server and client, which is required to handle subscription-type queries.
After adding the dependency to the pom.xml, we need to add the property „spring.graphql.websocket.path” in the application.properties file. This property is necessary because it informs Spring about the WebSocket path where subscription operations should be available. By default, Spring GraphQL uses the „/subscriptions” path, but you can customize it to suit your own needs.
In the case of a subscription query in the controller, we will use the @SubscriptionMapping annotation. Similar to @QueryMapping and @MutationMapping, this annotation can also be used without taking any parameters if our method in the controller has the same name as the GraphQL query.
The endpoint with this annotation will return an object of type Flux, which is a reactive type in Spring that represents a data stream and is suitable for handling such a query.
„Flux.interval(Duration.ofSeconds(2))” creates a data stream that generates a value every 2 seconds.
Then, „flatMap” is used to transform the value generated by Flux.interval. Inside „flatMap,” a new object is saved to the database.
„Flux.just(directorRepository.findAll())” creates a stream containing a list of Director objects. This stream returns updated data after saving a new object in the database.
„.take(Duration.ofSeconds(20))” limits the subscription’s duration to 20 seconds; after this time, the connection will be terminated.
Summary
GraphQL is an intriguing tool that can significantly streamline developers’ work. It allows for the creation of flexible queries, limiting data retrieval from the server to only what the user needs. Furthermore, its implementation is not complex, and its use is very similar to REST APIs, so developers will have no trouble transitioning to this technology. While GraphQL can be helpful in creating REST APIs, it has the potential to replace them.
Link to the GitHub repository from which the screenshots were presented in the article: https://github.com/jjarosz-cnv/graphql