IBM Developer

Article

A guide to GraphQL and development with Spring Boot

Explore GraphQL features and implementation with Spring Boot and IBM API Connect for efficient API development

By Shantanu Deshmukh, Sahana Y S

GraphQL, according to the official documentation, is a query language designed for APIs. GraphQL represents more than just an alternative to traditional REST APIs. It offers a versatile approach to querying backends with a flexible, data-source agnostic language. Unlike conventional APIs, GraphQL enables schema introspection, precise data control, built-in authentication, highly efficient multi-data-source queries, and much more.

For example, consider the following query in a GraphQL API:

POST http://localhost:8090/GraphQL
query {
  books {
    id
    title
    author
  }
}

The accompanying JSON-like payload constitutes the GraphQL query. This illustrates how clients can precisely request specific fields from the data retrieved from a given endpoint. This ability to tailor queries to exact requirements distinguishes GraphQL from its counterparts.

To learn more about GraphQL's capabilities, refer to the article, What is GraphQL. It is a valuable resource for understanding GraphQL comprehensively.

This article serves as a guide to the fundamental concepts of GraphQL, essential for beginners and those considering its implementation in production-grade applications. It also outlines the necessary components for building a GraphQL API using the Spring Boot framework. Additionally, a link to a code repository is provided, containing a reference application for readers to experiment with on their local machine.

Use cases for GraphQL

Despite the widespread adoption of REST as the de facto standard for API development over the past decade, the emergence of GraphQL raises the question: why explore alternative API specifications such as GraphQL? The answer lies in addressing several shortcomings inherent in REST APIs that GraphQL aims to rectify. Some of the notable issues include:

  • Over-fetching: Traditional REST APIs often return exhaustive datasets, containing all fields of an object or resource, even if the client requires only a subset of those fields. This over-fetching of data can lead to network bandwidth saturation and hinder the efficiency of client-server interactions.

  • The N+1 problem: REST APIs may sometimes under-fetch data, requiring multiple requests to fulfil a single client query. For example, in the absence of a specific REST endpoint for joining related data, clients may need to fetch data from multiple endpoints, resulting in a cumbersome and inefficient process that strains both network resources and backend databases.

  • Request validation: In REST APIs, the responsibility for validating request variables and payloads rests primarily on server-side logic. This places an additional burden on developers to ensure proper validation of request parameters.

  • Lack of flexibility and customization: REST APIs adhere to a rigid structure based on predefined HTTP methods, status codes, and response formats. This lack of flexibility can limit customization options for both clients and servers. For example, assembling a joined dataset from disparate resources often requires the creation of additional endpoints with complex SQL joins, adding complexity to the system.

  • Complexity and maintenance: REST APIs requires creation of multiple endpoints to cater to different resources, along with versioning to maintain backward compatibility. This raise of endpoints can significantly increase the complexity and maintenance overhead of the system.

  • Performance comparison: Performance benchmarks comparing GraphQL and REST APIs built using Spring Boot, querying the same MongoDB database with large datasets, reveal notable differences. Through rigorous testing using tools such as JMeter, GraphQL APIs consistently outperform REST APIs by up to 30%. This performance advantage can be attributed to GraphQL's precise data fetching capabilities, efficient use of data loaders, and exact-fetching approach, which optimize data retrieval from backends.

When to use GraphQL

GraphQL proves advantageous in various scenarios, particularly:

  • Building network-sensitive low-latency applications: For applications intended for devices such as mobile phones, smartwatches, and IoT devices, where bandwidth usage and latency are critical factors, GraphQL offers significant advantages. Its ability to fetch only selective fields allows for optimized data retrieval, minimizing network overhead. This feature is especially beneficial in environments with unreliable network connections, where conserving bandwidth is essential.

  • Handling complex data-intensive APIs: In scenarios such as blogs or social networking platforms, where fetching nested data structures such as posts with their associated comments and commenters is common, GraphQL shines. Its data-fetcher concept enables parallel retrieval of disjoint data sets, avoiding the sequential waterfall-like flow typical in REST APIs. Additionally, GraphQL's exact fetching capability facilitates flexible querying, allowing for efficient data retrieval from multiple tables. This flexibility accommodates diverse consumer requirements, whether they necessitate a minimal subset of fields or the entire dataset.

  • Developing APIs with aggregation patterns: Applications that aggregate data from various storage APIs, such as dashboards integrating data from logging services, consumption statistics backends, or third-party analytics tools, benefit from GraphQL's capabilities. By leveraging GraphQL, developers can construct APIs that efficiently aggregate data sets from multiple sources, streamlining data retrieval and enhancing overall efficiency.

When not to use GraphQL

While GraphQL offers numerous benefits, there are scenarios where it may not be the ideal choice:

  • Building simple APIs: When your application consistently requires all fields of the data source, opting for GraphQL may introduce unnecessary complexity. Features such as schema definitions, types, queries, mutators, resolvers, and other high-order components can add overhead, particularly in terms of maintenance. Tasks such as error handling and file uploads may also become more intricate. Additionally, GraphQL responses always return a status code of 200 regardless of the query's success, complicating error handling. Although libraries such as Apollo Client and GraphQL-upload offer solutions for error handling and file uploads, implementing them may not be as straightforward as with REST, and GraphQL-upload may not be available for all programming languages.

  • Data sources with few fields: In cases where your data source contains a limited number of fields, the complexity of GraphQL APIs may outweigh the benefits. Developing a GraphQL application requires implementing multiple components, including a GraphQL schema model, data fetchers, field resolvers, and custom scalars or data types. However, investing effort in fetching a dataset with few columns, especially for consumers who require the same view of the dataset, may not yield proportional returns. In such scenarios, the overhead of using GraphQL may not be justified.

Anti-patterns in GraphQL applications

To ensure the effectiveness and maintainability of GraphQL applications, it's important to avoid the following anti-patterns:

  • Lack of pagination strategy: Pagination plays an important role in managing large datasets. Failing to implement an efficient pagination strategy can lead to sluggish queries and overwhelm the client. Use GraphQL's cursor-based pagination feature to streamline data retrieval and enhance user experience.

  • Monolithic queries: While GraphQL allows clients to request only the necessary data, developers sometimes create monolithic queries that fetch excessive information. These queries demand extra processing power to parse and verify parameters, potentially impacting server performance. Implement mechanisms to manage query complexity, such as limiting query depths and avoiding recursion, to mitigate this issue.

  • Ignoring caching: Caching significantly improves response times and reduces server load. Neglecting caching mechanisms, such as using the @cacheControl directive, can result in redundant data requests and slower performance overall. Incorporate caching strategies to optimize data retrieval and enhance scalability.

  • Overlooking authorization and authentication: Security is crucial in API design. Neglecting proper authorization and authentication mechanisms exposes GraphQL endpoints to potential breaches. Implement robust authentication strategies and conduct thorough authorization checks to safeguard sensitive data.

  • Not using DataLoader: DataLoader is a crucial tool for optimizing data fetching in GraphQL. It efficiently batches and caches data requests, preventing the N+1 query problem. Integrate DataLoader into your GraphQL application to minimize database queries and improve performance.

  • Redundant type definitions: Defining multiple types that serve the same purpose can clutter the schema and confuse developers and consumers of the API. Maintain a concise and organized schema by eliminating redundant type definitions, ensuring clarity and ease of understanding.

  • Lack of documentation: Comprehensive documentation is essential for fostering adoption and integration of GraphQL APIs. Provide meaningful descriptions for types, fields, and arguments to facilitate easy comprehension and usage.

  • Circular dependencies: Circular dependencies between types can lead to conflicts and ambiguity in the schema. Structure your types carefully to maintain a clear hierarchy and avoid situations where types reference each other in a loop.

  • Ignoring error handling: Detailed error responses are essential for guiding developers when issues arise in GraphQL APIs. Neglecting proper error handling can frustrate developers and hinder the debugging process. Craft informative error messages and use union types for explicit error representation.

  • Not Planning for Schema Evolution: As requirements change, GraphQL schemas will evolve over time. Failing to plan for schema evolution can result in compatibility issues and disruptions for clients. Embrace versioning and using tools such as schema stitching to manage schema changes seamlessly, ensuring smooth transitions and continued functionality.

Features of GraphQL

GraphQL offers a powerful and flexible solution for designing APIs, driven by a schema-based approach rather than traditional resource and endpoint-based architectures.

Schema and type system

In GraphQL, the schema plays an important role, similar to how resources and endpoints drive a REST API. A GraphQL schema comprises various types and queries that define the operations supported by the API that is being designed. Each type is associated with a specific data or domain model that is used for retrieval or storage. Let's explore this with an example schema for an Employee type:

type Employee {
  id: Int!
  name: String!
  address: String
  department: Department!
  dataOfJoining: Date
  phoneNumber: Int
}

In the above schema snippet, we define the Employee type, specifying fields such as id, name, address, department, dateOfJoining, and phoneNumber. The exclamation mark denotes that these fields are non-null, ensuring data integrity.

GraphQL also features a robust data type system, including basic types such as Int, String, Float, Boolean, and ID. Custom types can be defined by specifying custom scalar types, such as scalar Date.

Relationships between types can also be defined within the schema. For example, the department field in the Employee type is of type Department, indicating a one-to-one relationship, similar to nodes and edges in a graph.

alt

While REST APIs typically use various HTTP methods such as GET, POST, PUT, and PATCH for querying, inserting, and updating data, GraphQL simplifies these operations into just two: Query and Mutation.

Query and mutation operations

In GraphQL, queries serve as the primary means of retrieving data from the API. These queries are defined within the Query type in the schema file. For example:

type Query {
    getAllCustomers: [Customer]!
    getCustomerById(customerId: String!): Customer
}

Here, we define queries that will be supported by our API, such as getAllCustomers and getCustomerById. The getCustomerById query takes a customerId argument, denoted as non-null by the exclamation mark.

Mutations, on the other hand, enable data insertion and updating operations. They are declared within the Mutation type in the schema, as shown below:

type Mutation {
    createCustomer(customer: Customer!): String
}

In this example, the createCustomer mutation specifies a query that accepts a Customer object as an argument, ensuring it cannot be null.

Scalars

Scalars represent primitive data types in GraphQL schemas. While GraphQL provides standard scalars like Int, Float, Boolean, String, and ID, custom scalars can also be defined to accommodate specific data types.

Extended scalars library

In addition to the standard GraphQL scalars, an extended scalars library offers specialized data types tailored for Java:

  • scalar BigDecimal
  • scalar BigInteger
  • scalar Byte
  • scalar Char
  • scalar Short
  • scalar Long

Custom scalars

When the predefined scalars don't meet requirements, custom scalars can be defined. Adding a custom scalar like Date to the schema involves the following steps:

Implementing a Date Scalar

Lets consider the same Employee type as follows

type Employee {
  id: Int!
  name: String!
  address: String
  department: Department!
  dataOfJoining: Date
  phoneNumber: Int
}

Above you can see the field dataOfJoining whose type is Date. Now this is the custom scalar we need to create.

In the code implementation, a private LocalDate dateOfJoining field must be added to the Employee class. Both the constructor of the class and the createEmployeeMutation resolver, in both code and schema, must be extended with a new dateOfJoining parameter.

The final step is defining the GraphQL scalar itself. This is achieved by providing a GraphQLScalarType bean as follows:

@Bean
public GraphQLScalarType dateScalar() {
    return GraphQLScalarType.newScalar()
        .name("Date")
        .description("Java 8 LocalDate as scalar.")
        .coercing(new Coercing<LocalDate, String>() {
            @Override
            public String serialize(final Object dataFetcherResult) {
                //logic to parse date into string
            }

            @Override
            public LocalDate parseValue(final Object input) {
                //logic to parse string value into LocalDate object
            }

            @Override
            public LocalDate parseLiteral(final Object input) {
                //logic to parse string value into LocalDate object
            }
        }).build();
}

With these steps, a custom Date scalar is integrated into the GraphQL schema, providing support for Java 8 LocalDate objects.

Queries and mutations

The primary means of interacting with a GraphQL API is through queries. A GraphQL query is typically submitted to the API endpoint via the POST method. Here's an example query:

{
  Employees(departmentId: 101) {
    id
    name
    department {
      id
      name
    }
  }
}

In this query, Employees is the name of the query defined within the type Query section of our schema file. Within the Employees block, we specify the fields we want in our result. These fields must be a subset of all the fields specified inside the type Employee, as defined in our GraphQL schema, outlined in the Schema and Type System section.

Notice that this query takes an argument of departmentId. In implementation, we can return employee records that match the departmentId value provided as that argument.

Mutations

While queries fetch data from the backend, mutations are used to insert or update data. Mutations are essentially a type of query and are defined as follows:

type Mutation {
  createEmployee(employee: EmployeeInput): Employee!
  createDepartment(id: Int!): Department!
}

In the above example, two mutation queries are declared: createEmployee and createDepartment. The createEmployee query takes an EmployeeInput object as an argument:

input EmployeeInput {
  id: Int!
  name: String
  address: String
  phoneNumber: String
  deptId: Int
}

The EmployeeInput type is similar to the Employee type. However, for mutations, the naming convention should be slightly different from the types involved in queries.

Directives

Directives offer a mechanism to enhance the language features of GraphQL through a supported syntax. They can be applied to various locations within a GraphQL document, including fields, fragments, operations, and even schema definitions, influencing their behavior based on their location.

Built-in directives

GraphQL provides several built-in directives such as @skip, @include, @deprecated, and @specifiedBy.

Example: @skip Directive

{
  allEmployees ($skipDepartment: Boolean!) {
    id
    name
    department @skip(if: @skipDepartment){
      id
      name
    }
  }
}

In the above query, the Department field is skipped if the skipDepartment variable is set to true during querying. Similarly, other directives such as @include can be used to include fields in the query dynamically.

@deprecated and @specifiedBy directives are used within the schema. They are helpful for declaratively specifying common server-side behaviours such as authorization requirements or marking sensitive data.

Example: @deprecated Directive

type User {
  id: ID!
  name: String! @deprecated(reason: "Use the firstName and lastName field")
  firstName: String!
  lastName: String!
  email: String!
}

Here, @deprecated warns that the name field is deprecated and advises using the firstName and lastName fields instead.

While query directives allow clients to define metadata and occasionally behaviour, schema directives enable the server to define behaviour, validation, and resolution logic in a declarative manner.

Custom directives

You can create custom directives using DSL directives and Directive wiring.

Example:@auth Directive

directive @auth(role : String!) on FIELD_DEFINITION

type Employee
    id : ID
    name : String!
    startDate : String!
    salary : Float @auth(role : "manager")
}

In this example, the @auth directive specifies that only users with the role manager are authorized to access the salary field. This directive can be applied to any field requiring manager role authorization.

Custom directives provide a convenient way to enforce specific authorization rules or define custom behaviors within your GraphQL schema. You can explore examples of custom directives in the reference application (towards the end).

Solving the N+1 problem in GraphQL

The N+1 problem is a common issue encountered with unoptimized APIs. It occurs when an API executes multiple database calls to retrieve details of one entity, followed by a similar number of calls to fetch details of entities linked to the original one.

For example, consider the Employee and Department models. Each employee is associated with a department. When a user queries to fetch both employee and department details, the API first retrieves all employees from the database. Then, it makes separate database queries to fetch department details for each employee.

In GraphQL, we can address this issue by using Data Loaders. A Data Loader is a handler function triggered by the GraphQL framework to fetch additional data related to an entity. For example, to get department details for each employee, we define a data loader for the department associated with the Employee type:

@BatchMapping(typeName = "Employee")
public Mono<Map<Employee, Department>> department(Set<Employee> employeeSet) {

After fetching employee records, the GraphQL framework automatically triggers the data loader function by passing a collection of fetched employees. Each employee object within this collection contains a departmentId field. We aggregate the values of this field from each employee object into a list. Then query the Department table for all the collected department Ids. Subsequently, we map the department details to individual employees by grouping them based on departmentId.

This approach allows us to efficiently retrieve all department details at once and assign them to the result set of employees that we fetched earlier. By using Data Loaders, we can mitigate the N+1 problem and optimize data fetching in GraphQL APIs.

Precise data retrieval from the backend

One of the most significant advantages of GraphQL compared to traditional REST APIs is its ability to prevent over-fetching or under-fetching of data. This means that with GraphQL, consumers can request exactly the number of columns or fields they need.

In a REST API scenario, a request like GET /employee would typically result in the API fetching all columns of the Employee table from the database and returning the entire result to the consumer. However, what if the consumer only requires a couple of fields from the Employee table instead of the dozens available? In such cases, the consumer would have to sift through and discard unwanted fields, putting unnecessary stress on both the database and network.

In GraphQL, consumers can specify precisely which fields they need. For example, consider the following query:

{
  allEmployees {
    id
    name
  }
}

Here, the query specifies that only the id and name fields should be returned for all employees. Even if the Employee type, as defined in the Schema and Type System section, contains a total of six fields, this query requests only two. If an application frequently requires only these two fields via a GraphQL API, it can optimize its network bandwidth by fetching only the specific fields mentioned in the query.

Schema introspection

Schema introspection is a powerful feature that enables the discovery of available queries, mutations, subscriptions, types, and fields within a specific GraphQL API. Introspection queries typically start with __ , indicating their special nature.

By using schema introspection, developers can gain insight into all the operations available in the API, facilitating efficient query construction and preventing over-fetching or under-fetching of data.

Some of the key introspection queries include:

  • __schema
  • __type
  • __typename

Here's an example of how schema introspection can be used to explore available queries, mutations, and types:

{
 __schema {
   types {
     name
     description
   }
 }
}

In the preceding query, the __schema field is used to retrieve information about the available types in the schema, including their names and descriptions. Similarly, developers can customize introspection queries to fetch details about mutations, subscriptions, and more, enabling comprehensive exploration of the GraphQL API.

Subscriptions

Subscriptions in GraphQL enable consumers to subscribe to dynamic data sources, facilitating real-time updates as new data arrives. This mechanism allows changes occurring in a data source to be immediately relayed back to subscribers, ensuring timely access to the latest information.

To add a subscription in the GraphQL schema, you can define it as follows:

subscription StockCodeSubscription {
  stockQuotes(stockCode:"IBM") {
    dateTime
    stockCode
    stockPrice
    stockPriceChange
  }
}

Implementing subscriptions typically involves establishing long-running connections between a GraphQL server and clients/subscribers using WebSocket technology. Clients can subscribe to receive updates and unsubscribe when no longer needed. The server, on the other hand, can also unsubscribe due to errors or timeouts. Since subscriptions are stateful, they require maintaining the GraphQL document, variables, and contexts over the lifetime of the subscription.

For example, as illustrated in the following figure, the server must retain crucial subscription details such as requested fields, associated clients, and any relevant parameters. This information is essential for the server to accurately process and respond to events, ensuring that subscribers receive updates tailored to their specific subscriptions.

alt

When to use subscriptions

  • Incremental updates to large Objects: Subscriptions are ideal for scenarios where clients need incremental updates to large objects. Instead of polling the server repeatedly, clients can fetch the object once and then subscribe to changes for specific fields, reducing unnecessary network traffic.

  • Real-time low latency updates: For applications requiring real-time updates with low latency, subscriptions are invaluable. Use cases include chat applications, stock market updates, or any scenario where timely data updates are critical.

Subscriptions allow GraphQL APIs to deliver dynamic, real-time experiences to clients, ensuring they stay synchronized with the latest changes in the underlying data source.

IBM API Connect + GraphQL

IBM API Connect serves as a comprehensive API management solution, facilitating the entire lifecycle of APIs with user-friendly features designed to streamline the creation, management, security, and monetization of APIs across diverse environments. This platform supports digital transformation efforts by providing robust platform for on-premises and cloud-based applications. To explore more about its capabilities, see IBM API Connect.

Integration with GraphQL in IBM API Connect enhances application development by enabling developers to efficiently create, manage, and secure GraphQL APIs, effectively bridging the gap between data sources and applications. Here's how IBM API Connect facilitates GraphQL-based applications:

  • Creation and testing of GraphQL APIs: IBM API Connect simplifies the creation and deployment of GraphQL APIs, allowing developers to build production-ready GraphQL APIs quickly. With intuitive tools, developers can compose GraphQL schemas and configurations effortlessly. The platform provides features such as the embedded GraphQL interface for query testing and the local test environment for local API testing without server connectivity. Learn more about building GraphQL APIs using IBM API Connect in this informative video: Build GraphQL APIs. Explore further details on creating and testing APIs with IBM API Connect from the Create New APIs and API Development section.

  • API management: IBM API Connect offers robust API management capabilities throughout the API lifecycle. From grouping APIs into products and staging versions in different environments to controlling access for developers and managing version updates, the platform ensures comprehensive API lifecycle management. Gain insights into API usage and community growth while targeting new markets effectively. Discover more about managing APIs with IBM API Connect in the Manage your APIs and API Manager section.

  • AI-powered automation testing: Simplify API testing with IBM API Connect's AI-driven AutoTest Assist feature, which generates thousands of test requests automatically, eliminating the need for manual test case creation. This AI-powered testing significantly enhances developer productivity during the API development phase. Explore the benefits of AI and automation testing in API testing.

  • API Security: Ensure robust security for your APIs with IBM API Connect's advanced security capabilities. Use authentication and authorization mechanisms such as OAuth, OpenID Connect, and third-party services to control access to APIs effectively. Learn more about securing APIs with IBM API Connect in the Secure your APIs section.

In summary, IBM API Connect streamlines the development, testing, management, and security of GraphQL APIs, offering a comprehensive solution for building production-grade GraphQL application ecosystems.

Development with Spring Boot

To explore GraphQL concepts further, a reference application has been created specifically to showcase the various aspects mentioned in this article. This application, developed using Java and the Spring Boot GraphQL library, is available for examination at GitHub.

In the aforementioned reference application, Spring Boot provides a starter dependency, spring-boot-starter-graphql, to initiate GraphQL application development, abstracting away many low-level implementations for complex GraphQL concepts such as data-fetchers and field resolvers. Using Spring Boot, GraphQL applications can be developed seamlessly by following these key stages:

  1. Maven dependencies: Begin by adding the spring-boot-starter-graphql dependency to your pom.xml file. This dependency integrates the graphql-java library and the graphql-spring-boot-starter module, providing core functionality and integration for GraphQL and Spring Boot.

     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-graphql</artifactId>
     </dependency>
     <!-- For testing support, include the following dependency -->
     <dependency>
         <groupId>org.springframework.GraphQL</groupId>
         <artifactId>spring-graphql-test</artifactId>
         <scope>test</scope>
     </dependency>
    
  2. Define your GraphQL schema: Write GraphQL schemas and store them in files with a .graphqls extension under the src/main/resources folder. These files contain type definitions, queries, and mutations for your API. Spring-GraphQL framework auto-detects these files and loads them at runtime. For example, define a Book type, a Query type with a books field, and a Mutation type with a createBook field.

     type Book {
       id: ID!
       title: String!
       author: String!
     }
    
     type Query {
       books: [Book]!
     }
    
     type Mutation {
       createBook(title: String!, author: String!): Book!
     }
    
  3. Implement DataFetcher interfaces: Implement DataFetcher interfaces for each field in your schema to handle logic for fetching and modifying data from your data source. You can register these implementations as Spring beans and use annotations such as @GraphQLQuery or @GraphQLMutation to map them to corresponding fields in your schema.

    Note: This can be entirely skipped, as Spring GraphQL library can opinionatedly wire up these interfaces automatically for us. For some more nuanced and detailed handling we can manually declare and define these.

    For example, you can implement a BookDataFetcher class that will fetch all the books from a BookRepository and a CreateBookDataFetcher class that will create a new book and save it to the repository, as shown below:

     @Component
     public class BookDataFetcher implements DataFetcher<List<Book>> {
    
       @Autowired
       private BookRepository bookRepository;
    
       @Override
       @GraphQLQuery(name = "books")
       public List<Book> get(DataFetchingEnvironment dataFetchingEnvironment) {
         return bookRepository.findAll();
       }
     }
    
     @Component
     public class CreateBookDataFetcher implements DataFetcher<Book> {
    
       @Autowired
       private BookRepository bookRepository;
    
       @Override
       @GraphQLMutation(name = "createBook")
       public Book get(DataFetchingEnvironment dataFetchingEnvironment) {
         String title = dataFetchingEnvironment.getArgument("title");
         String author = dataFetchingEnvironment.getArgument("author");
         Book book = new Book();
         book.setTitle(title);
         book.setAuthor(author);
         return bookRepository.save(book);
       }
     }
    
  4. Create controllers: Develop controllers to handle GraphQL requests and responses. Use the @Controller annotation to mark the class as a GraphQL controller and the @RequestMapping annotation to map HTTP requests to specific path. If you don’t specify @RequestMapping annotation /graphql is taken as default. You can use the @RequestBody annotation to retrieve the raw GraphQL query from the request body and execute the query.

     @Controller
     public class BooksController {
    
       private BookRepository bookRepository;
    
       Public BooksController(BookRepository bookRepository) {
         this.bookRepository = bookRepository;
       }
    
       @QueryMapping(“books”)
       public List<Book> execute(@RequestBody String query) {
         ExecutionResult result = GraphQL.execute(query);
         return new ResponseEntity<>(result, HttpStatus.OK);
       }
     }
    
  5. Write data loaders: Use DataLoader interface to batch up data required for further queries, mitigating the infamous n+1 data fetching problem. For example, if a book references multiple authors, DataLoader can batch up author IDs and fetch them from the database all at once.

     @BatchMapping(typeName=”Trade”)
     public Map<Book,List<Author>> authors(List<Book> books) {
       Set<String> authorIds = books.stream().map(Book::authorId).collect(Collectors.toSet());
       Set<Author> authors = authorRepository.findByIds(authorIds);
       return books.stream()
         .collect(toMap(
             book -> book,
             book -> authors.stream().filter(author -> author.getAuthorId().equals(book.getAuthorId())).findFirst().get()
           )
         );
     }
    
  6. Run and test your application: Use tools such as Postman, Insomnia, or GraphiQL to send queries and mutations to your API and observe the results. For example, send a query to retrieve all books from your database.

     POST http://localhost:8090/graphql
    
     query {
       books {
         id
         title
         author
       }
     }
    

    You can also use the GraphiQL interface, bundled with the spring-boot-starter-graphql dependency, to test your API. Simply navigate to http://localhost:8090/graphiql in your browser to access this utility, equipped with a query and type explorer.

    The following figure shows the GraphiQL utility. The sidebar on the left side includes:

    1. Schema explorer: Shows all types and scalars listed in GraphQL schema
    2. Query history: Lists all the queries you have run so far
    3. Query explorer: Shows all GraphQL queries in the schema. You can select any query and expand upon its various elements like fields it exposes, the arguments that it takes and interactively build a query

      In the middle, you can see the query that has formed by picking elements from query explorer window. You can also edit the query in this window. If you need to pass in any headers or query variables, you can do that in the bottom portion of that same window. the right side window shows the query results.

      alt

References