An alternative to REST API - GraphQL

An alternative to REST API - GraphQL

In this article, we'll learn about Spring GraphQL. It is built on top of GraphQL Java. It supports request handling over HTTP, WebSocket, and RSocket. With the help of GraphQL, we can create GraphQL architecture-style API.

In most microservice-based applications, we use REST API. One of the common problems with REST API is over-fetching and under-fetching data. This happens because it will return all the fields available on the response whenever we hit any specific endpoint. It is server-centric so it won't allow the client to select specific fields. Another major problem with REST API is that it provides a separate URL to access specific data, so if we want multiple types of data, then we need to make multiple calls to the server.

GraphQL architecture helps us solve the above problems. It is client-centric, so we can specify the field in which we are interested, so it will not return more or less than the specified field in the request. We can deploy it using a single endpoint so we can get multiple kinds of data using a single call.

We'll create a spring boot server that will expose endpoints in GraphQL architecture as part of this demo. I have added a code snippet here for the full code reference refer GITHUB

Prerequisites

  • JDK 17+
  • Maven
  • Basic knowledge of Java, Spring boot

GraphQL server

  • Create a spring boot web application and add GraphQL dependency

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-graphql</artifactId>
           </dependency>
    
  • As part of this demo we will fetch data from the H2 database so add a dependency for H2 database and Spring data JPA
  • Add DepartmentRepo and EmployeeRepo that will help to interact with the database through spring data JPA
  • Add some dummy data on application startup. Each department can have multiple employees and each employee can be part of one department.

    @Bean
      public CommandLineRunner CommandLineRunnerBean(DepartmentRepo departmentRepo, EmployeeRepo employeeRepo) {
          return (args) -> {
              Department it = departmentRepo.save(new Department("IT"));
              Department admin = departmentRepo.save(new Department( "Admin"));
              Department hr = departmentRepo.save(new Department( "HR"));
    
              employeeRepo.save(new Employee("Yogesh", it));
              employeeRepo.save(new Employee("Raj", admin));
              employeeRepo.save(new Employee("Tom", hr));
    
              employeeRepo.findAll().forEach(System.out::println);
              departmentRepo.findAll().forEach(System.out::println);
    
          };
      }
    
  • Create a file schema.graphqls under src/main/resources/graphql
  • Add below content for schemas. All the select queries will be part of the type Query and update or insert will be part Mutation. Here, we do have a couple of select queries and one mutation. The first select department will return a list of the departments and the other is to get employee details based on employee ID. As this is GraphQL server so we need to write all the possible fields in the schema. Clients or consumers can select based on their requirements. We are adding an employee as part of the addEmployee, it will take the employee's name and department ID as input and return the employee as an object.

      type Query {
          departments : [Department]
          employeeById(id : ID!) : Employee
      }
    
      type Department {
          id : ID!
          name : String
          employees : [Employee]
      }
    
      type Employee {
          id : ID!
          name : String
          department : Department
      }
    
      type Mutation {
          addEmployee(employee : EmployeeInput) : Employee
      }
    
      input EmployeeInput {
          name: String
          departmentId : ID!
       }
    
  • Add below properties on src/main/resources/application.properties. It will help us create schema on the application startup and enable GraphIQL UI so we can execute all the requests.

    spring.jpa.hibernate.ddl-auto=create-drop
    spring.graphql.graphiql.enabled=true
    
  • Add below GraphQLController controller. We need to add all the methods that we have defined in the schema file with their implementation. For selection, we need to annotate with @QueryMapping and for insert or update annotate with @MutationMapping. While creating employees we need to get multiple fields as part of the input request, so created a record. It can be any normal class with getters and setters.

    @QueryMapping
      Iterable<Department> departments(){
          return departmentRepo.findAll();
      }
    
      @QueryMapping
      Optional<Employee> employeeById(@Argument Long id){
          return employeeRepo.findById(id);
      }
    
      @MutationMapping
      Employee addEmployee(@Argument EmployeeInput employee){
          Optional<Department> department = departmentRepo.findById(employee.departmentId());
          return employeeRepo.save(new Employee(employee.name(), department.get()));
      }
      record EmployeeInput(String name, Long departmentId) {
      }
    

Test from GraphQL UI

  • Use localhost:8080/graphiql?path=/graphql GraphQL URL to access any API call.
  • As GraphQL is single endpoint-based we can get multiple types of data within a single call.
  • Add the below query as part of the input request to get a list of the department and employees by id. If we do not want any specific field in the response, then we can just delete it from the input request.

    query {
    departments {
      id
      name
      employees {
        id
        name
      }
    }
    employeeById(id : 4){
      id
      name
      department{
        id
        name
      }
    }
    }
    
  • Output response will be as below with both types of data in a single call.

  • graphiql.png

  • Add below as input for adding an employee.

    mutation {
    addEmployee(employee : {
      name: "Dom",
      departmentId: 1
    }){
      id
    }
    }
    
  • Output response will be as below with ID as we have specified in the input request.

  • graphql-2.png