A BLOG POST
GraphQL now and thank me later!
As a developer, you are constantly faced with the challenge of building APIs that are flexible, efficient, and easy to use. In the past, REST has been the go-to choice for building APIs, but a new player has recently emerged on the scene: GraphQL.
But what is GraphQL, and why has it gained so much popularity in recent years So, let's dive in and see what all the fuss is about!
11/4/2024, 2:10:11 AM
489 times
GraphQL is a query language for your API that allows the client to request specific data, rather than getting a fixed set of data from the server. It was developed by Facebook in 2012 and was released as an open source project in 2015. Since then, it has gained popularity among developers as an alternative to REST APIs.
If you're familiar with REST, then you might probably ask how they are different from each other. At first, I thought that GraphQL was a new thing that would replace REST. We used to have CORBA, RPC, SOAP, then REST came around in 2000 and everything blown away!
Now, REST seems to be a standard mechanism in information exchange between systems. So, when GraphQL appeared, I also thought that it would become the new de facto in information exchange mechanism. But in reality, GraphQL is not a straightforward "replacement" for REST.
I'm sure you fellowdevs are already very familiar with REST or RESTful APIs. But I want us to understand more deeply what a REST API actually is.
RedHat defines a REST API as:
A REST API (also known as RESTful API) is an application programming interface (API or web API) that conforms to the constraints of REST architectural style and allows for interaction with RESTful web services.
REST is a set of constraints or guidelines. An API can be called a RESTful API if it meets certain criteria (read the source).
One of the main concepts behind REST is resources. And what is a Resource? Simply put, a Resource is the response we desire when creating/using an API.
Resources are represented as URLs and can be accessed using HTTP methods such as GET
, POST
, PUT
, and DELETE
. REST APIs are typically organized around a set of resources, with each resource being a logical representation of a specific piece of data or functionality.
A resource in REST is a logical representation of a specific piece of data or functionality, and can be thought of as a noun (e.g. /users
, /posts
).
Meanwhile, GraphQL is a query language for APIs. It allows clients to request specific data from the server and provides a flexible way to expose data from a variety of sources.
In contrast, GraphQL does not have the concept of fixed resources like REST APIs. Instead, GraphQL uses a schema to define the types and fields of data that are available on the server. Clients can then send GraphQL queries to the server to request specific data, and the server will return the requested data in a JSON format. GraphQL itself requires a runtime to be able to translate the query into existing data.
While it is possible to think of the data returned by a GraphQL query as a "resource," the concept of a resource in GraphQL is somewhat different from the concept of a resource in REST.
In GraphQL, clients have more control over the data that is returned and can request specific data using a flexible query language, rather than being limited to fixed resources and HTTP methods.
One of the main differences between GraphQL and REST is that, with REST, the server determines the shape of the data that is sent to the client, while with GraphQL, the client can specify exactly what data it needs. This makes GraphQL more flexible and efficient, as the client only receives the data that it needs, rather than getting a fixed set of data that may include a lot of unnecessary fields. This comes to where REST API Data fetching problems are, we usually call them under-fetching and over-fetching.
Under-fetching occurs when a client needs to make multiple API requests to retrieve all the data it needs, resulting in a slow and inefficient process. For example, consider in a blogging application, an app needs to display the titles of the posts of a specific user. The same screen also displays the names of the last 3 followers of that user.
With a REST API, we would typically gather the data by accessing multiple endpoints. In the example, these could be /users/<id>
endpoint to fetch the initial user data. Secondly, there’s likely to be a /users/<id>/posts
endpoint that returns all the posts for a user. The third endpoint will then be the /users/<id>/followers
that returns a list of followers per user.
This can be slow and inefficient, especially if the client needs to retrieve a large number of comments.
Over-fetching occurs when a client retrieves more data than it needs, resulting in unnecessary network usage and slower performance. For example, in the illustration above the client needs to retrieve only the title of a blog post, but the API returns the entire post object including the comments and metadata. Then, while fetching the followers data, the client only needs to display the name of the followers. But here again, the client is over-fetching data.
From the illustration above, the client sends a request to a specific endpoint. Different resources mean different endpoint and different call. While with GraphQL, the client sends a query to a single endpoint and gets back a JSON object. This means that with GraphQL, the client can make multiple requests to different parts of the API in a single request, while with REST, the client would need to make separate requests to different endpoints.
Source: https://www.howtographql.com/basics/1-graphql-is-the-better-rest/
As a developer, I want to take this into a proven and understandable example in code which will be written using Spring Boot as I mainly use it (I'm not gonna explain it from the beginning though!).
The scenario is that we will build 3 API endpoints:
- Get list of users
- Get one specific user detail
- Create new user
To create a REST API in Spring, you will need to create a Spring Boot application and define a few endpoints.
java
1@RestController
2@RequestMapping("/users")
3public class UserController {
4
5 private UserRepository userRepository;
6
7 public UserController(UserRepository userRepository) {
8 this.userRepository = userRepository;
9 }
10
11 @GetMapping
12 public List<User> getUsers() {
13 return userRepository.findAll();
14 }
15
16 @GetMapping("/{id}")
17 public User getUserById(@PathVariable Long id) {
18 return userRepository.findById(id).orElse(null);
19 }
20
21 @PostMapping
22 public User createUser(@RequestBody User user) {
23 return userRepository.save(user);
24 }
25}
This controller defines three endpoints to meet the scenario.
Now to create a GraphQL API in Spring, you will need to use the GraphQL Java library and define a GraphQL schema.
First, you will need to create a GraphQL server:
java
1@Configuration
2public class GraphQLConfiguration {
3
4 private UserRepository userRepository;
5
6 public GraphQLConfiguration(UserRepository userRepository) {
7 this.userRepository = userRepository;
8 }
9
10 @Bean
11 public GraphQL graphQL() {
12 GraphQLObjectType queryType = GraphQLObjectType.newObject()
13 .name("query")
14 .field(GraphQLFieldDefinition.newFieldDefinition()
15 .name("users")
16 .type(new GraphQLList(userType()))
17 .dataFetcher(environment -> userRepository.findAll())
18 .build())
19 .field(GraphQLFieldDefinition.newFieldDefinition()
20 .name("user")
21 .type(userType())
22 .argument(GraphQLArgument.newArgument()
23 .name("id")
24 .type(Scalars.GraphQLID)
25 .build())
26 .dataFetcher(environment -> {
27 String id = environment.getArgument("id");
28 return userRepository.findById(Long.valueOf(id)).orElse(null);
29 })
30 .build())
31 .build();
32
33 GraphQLObjectType mutationType = GraphQLObjectType.newObject()
34 .name("mutation")
35 .field(GraphQLFieldDefinition.newFieldDefinition()
36 .name("createUser")
37 .type(userType())
38 .argument(GraphQLArgument.newArgument()
39 .name("user")
40 .type(userInputType())
41 .build())
42 .dataFetcher(environment -> {
43 UserInput userInput = environment.getArgument("user");
44 User user = new User();
45 user.setName(userInput.getName());
46 user.setEmail(userInput.getEmail());
47 return userRepository.save(user
48 );
49 })
50 .build())
51 .build();
52
53 GraphQLSchema schema = GraphQLSchema.newSchema()
54 .query(queryType)
55 .mutation(mutationType)
56 .build();
57
58 return GraphQL.newGraphQL(schema).build();
59 }
60
61 private GraphQLObjectType userType() {
62 return GraphQLObjectType.newObject()
63 .name("User")
64 .field(GraphQLFieldDefinition.newFieldDefinition()
65 .name("id")
66 .type(Scalars.GraphQLID)
67 .build())
68 .field(GraphQLFieldDefinition.newFieldDefinition()
69 .name("name")
70 .type(Scalars.GraphQLString)
71 .build())
72 .field(GraphQLFieldDefinition.newFieldDefinition()
73 .name("email")
74 .type(Scalars.GraphQLString)
75 .build())
76 .build();
77 }
78
79 private GraphQLInputObjectType userInputType() {
80 return GraphQLInputObjectType.newInputObject()
81 .name("UserInput")
82 .field(GraphQLInputObjectField.newInputObjectField()
83 .name("name")
84 .type(Scalars.GraphQLString)
85 .build())
86 .field(GraphQLInputObjectField.newInputObjectField()
87 .name("email")
88 .type(Scalars.GraphQLString)
89 .build())
90 .build();
91 }
92}
This configuration creates a GraphQL server with a query type that has two fields: users
, which returns a list of users, and user
, which returns a specific user by ID
. It also has a mutation type with a field called createUser
, which allows the client to create a new user.
To execute queries and mutations, you will need to create a GraphQL controller:
java
1@RestController
2@RequestMapping("/graphql")
3public class GraphQLController {
4 private GraphQL graphQL;
5
6 public GraphQLController(GraphQL graphQL) {
7 this.graphQL = graphQL;
8 }
9
10 @PostMapping
11 public Map<String, Object> executeQuery(@RequestBody Map<String, String> request) {
12 ExecutionResult result = graphQL.execute(ExecutionInput.newExecutionInput()
13 .query(request.get("query"))
14 .operationName(request.get("operationName"))
15 .variables(request.get("variables"))
16 .build());
17 return result.toSpecification();
18 }
19}
Note that this controller has only a single endpoint that accepts a POST
request with a JSON body that contains the GraphQL query or mutation. It then executes the query or mutation using the GraphQL server and returns the result to the client.
To compare the two APIs, you can create a simple client that sends requests to both the REST and GraphQL APIs and compares the results. Or just by simply use Postman or curl
to do it.
Here is a simple example for testing purpose:
java
1public class ApiClient {
2 private RestTemplate restTemplate = new RestTemplate();
3
4 public List<User> getUsersFromREST() {
5 return restTemplate.getForObject("http://localhost:8080/users", List.class);
6 }
7
8 public User getUserByIdFromREST(long id) {
9 return restTemplate.getForObject("http://localhost:8080/users/" + id, User.class);
10 }
11
12 public User createUserWithREST(User user) {
13 return restTemplate.postForObject("http://localhost:8080/users", user, User.class);
14 }
15
16 public Map<String, Object> executeQueryWithGraphQL(String query) {
17 HttpHeaders headers = new HttpHeaders();
18 headers.setContentType(MediaType.APPLICATION_JSON);
19
20 HttpEntity<String> requestEntity = new HttpEntity<>(
21 "{\"query\": \"" + query + "\"}",
22 headers
23 );
24
25 ResponseEntity<Map> responseEntity = restTemplate.exchange(
26 "http://localhost:8080/graphql",
27 HttpMethod.POST,
28 requestEntity,
29 Map.class
30 );
31
32 return responseEntity.getBody();
33 }
34}
This client has methods for making requests to the REST API and the GraphQL API. You can then use these methods to send requests and compare the results.
Now let's create a test class that sends a request to get a list of users from both the REST and GraphQL APIs and compares the results:
java
1@Test
2public void testGetUsers() {
3 ApiClient apiClient = new ApiClient();
4 // Test REST API
5 List<User> usersFromREST = apiClient.getUsersFromREST();
6 assertEquals(3, usersFromREST.size());
7 assertEquals("Alice", usersFromREST.get(0).getName());
8 assertEquals("Bob", usersFromREST.get(1).getName());
9 assertEquals("Charlie", usersFromREST.get(2).getName());
10
11 // Test GraphQL API
12 Map<String, Object> resultFromGraphQL = apiClient.executeQueryWithGraphQL("{ users { name } }");
13 List<Map<String, String>> usersFromGraphQL = (List<Map<String, String>>) resultFromGraphQL.get("data");
14 assertEquals(3, usersFromGraphQL.size());
15 assertEquals("Alice", usersFromGraphQL.get(0).get("name"));
16 assertEquals("Bob", usersFromGraphQL.get(1).get("name"));
17 assertEquals("Charlie", usersFromGraphQL.get(2).get("name"));
18}
I hope this example can give you better understanding in Spring Boot approach to implement the two mechanism.
Now that you have seen the comparison between the two, it might tempting to start moving on and use GraphQL starting today. But wait! I would like to quote one of my favorite talk regarding this topic. It is from Rob Crowley and said:
There is no universal best API style. There is always a best API style for your problem. Don't choose a technology and fit a problem to it. Let's take a look of the characteristics of your system and work backwards.
With that being said, there are a few potential drawbacks to using GraphQL compared to REST:
Setting up and maintaining a GraphQL API can be more complex than a REST API, as the server needs to be able to execute the queries that are sent by the client. This can require more development effort and may be more difficult for new developers to learn.
The example code above can be a proven steps through the line of codes and the usage of more interfaces and classes than creating REST API.
It can be more difficult to cache GraphQL data, as the client can request specific fields rather than a fixed set of data. This can make it harder to take advantage of caching to improve performance. However, Rob Crowley once again tackle this argument which I will explain in different post.
There may be less tooling and support available for GraphQL compared to REST, as GraphQL is a newer technology. This can make it more difficult for developers to find resources and guidance for working with GraphQL.
So whether or not developers should move to GraphQL depends on their needs and the requirements of their projects. It's also worth to remind as I wrote before that GraphQL is not a replacement for REST, but rather an alternative that may be more suitable for certain types of projects. Developers should consider their specific needs and the pros and cons of each approach before deciding which one to use.
There are a few situations in which GraphQL may be a better choice than REST:
- When the client needs a flexible and efficient way to request specific data: Because the client can specify exactly what data it needs with GraphQL, it can be a more flexible and efficient way to request data from the server. This can be particularly useful for mobile applications, where network bandwidth is limited and the client may not need all of the data that is provided by the server.
- When the API needs to support multiple clients with different data requirements: With GraphQL, each client can request the specific data that it needs, rather than getting a fixed set of data from the server. This can be useful when the API needs to support multiple clients with different data requirements, as it allows each client to get only the data that it needs.
- When the API needs to be self-documented: GraphQL includes a built-in documentation system, as the client can request the schema of the API to see the types of data that are available and the fields that can be queried. This can make it easier for developers to understand and use the API, as they don't have to refer to separate documentation or make trial-and-error requests to figure out what data is available.
I know that the title of this blog post seems a little bit unbalanced and more likely to promote GraphQL over REST. However, believe me, I'm not trying to!
Thus, I've collected some resources that backed my writings and will talk about it in my this post.