Blog
navigate_next
Java
Getting Started → GraphQL with SpringBoot
Gaurav Sharma
June 17, 2024

Chapter 1: Understanding GraphQL

If you're in the tech industry, chances are very high that you've heard of GraphQL, even if you haven't used it yet. It's one of those buzzwords that's constantly making the rounds. In this article, we'll dive into the world of GraphQL, exploring what makes it so special and how you can seamlessly integrate it with Spring Boot.

In simple words, GraphQL is a query language for APIs that works over HTTP and uses JSON for data exchange. It lets developers ask for exactly the data they need, making sure they get just that – no more, no less. This prevents over-fetching or under-fetching of data.

QL in GraphQL stands for Query Language(Graph Query Language). Developed by Facebook(Meta) in 2012 and open-sourced in 2015, GraphQL provides a more efficient, powerful, and flexible alternative to REST.

GraphQL is extensively used by numerous companies and organizations, including Facebook, Netflix, Shopify, PayPal, Instagram, and many others.

💡 GraphQL is highly powerful for executing complex queries and cutting down the number of API calls, but it has a significant trade-off: a steep learning curve. However, don't worry, this article is written in such a manner that by the end of this article, you'll have a solid grasp of what GraphQL is, how it differs from REST, how to set up a GraphQL server with Spring Boot, and how to use GraphiQL to run queries and mutations.

💡 GraphQL works between the client and backend services, making data retrieval more efficient. Instead of making multiple requests like you do with REST, you can ask for everything you need in one go with GraphQL. This means you get all your data in a single request, making the process quicker.

Official Definition: GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.

💡 GraphQL Should Not Be Confused with SQL Despite Both Being Query Languages.SQL is a query language used for managing and retrieving data from relational databases with predefined tables and schemas. GraphQL is a query language for APIs that allows clients to request specific data, often from multiple sources, in a single, flexible query.

What is a Runtime in GraphQL?

A runtime in GraphQL is the part of the system that takes the query you write and gets the data you asked for. Here’s how it works:

You write a GraphQL query: For example, you ask for a person’s name and age.

GraphQL runtime processes the query: It reads your request and figures out how to get the data.

Fetches the data: The runtime contacts the database or other data sources, gets the requested information, and formats it correctly.

Returns the data to you: You get exactly what you asked for, nothing more, nothing less.

Chapter2: Components of a GraphQL Endpoint

  1. Schema:
    • The schema is the backbone of a GraphQL endpoint. It acts as an agreement between the client and the server, defining what types of data are available and how they relate to each other.
    • For an analogy, think of it as the menu card in a restaurant. It lists all the dishes (types of data) you can order and how they are categorized.
  2. Queries:
    • Queries are used to ask for specific data. You specify exactly what you need, and the server returns that data. Queries are used for searching and fetching only. To modify any data, we use mutations.
    • The response contains exactly what we specified in the query, with no additional or missing data.
  3. Resolvers:
    • Resolvers are functions that fetch the data requested by a query. They know how to get the data and return it in the correct format.
  4. Mutations:
    • Mutations are used to change data, like adding, updating, or deleting entries.
  5. Subscriptions:
    • Subscriptions provide real-time updates when data changes. You can subscribe to certain events and get notified immediately.

💡 In essence, when working with GraphQL, we primarily deal with three key concepts: schema, query, and mutation. The schema acts like a menu in a restaurant, defining the structure of available data and operations. Queries are used to retrieve or fetch data from the server, similar to ordering from the menu. On the other hand, mutations enable us to modify or change data, encompassing actions such as adding new items, deleting existing ones, or updating information.

We will delve into these components later in the article.

Chapter 3: Rest vs GraphQL

REST and GraphQL are two different approaches to fetching data from a server, each with its own advantages and disadvantages.

💡 With Rest, you need to make three separate requests to get all the information. Each request might include more data than you need (over-fetching) or might require additional requests to get missing data (under-fetching).

Understanding with an example:

Imagine you're building a social media app and you need to display a user's profile. The profile should show the user's name, email, posts, and friends.

REST Approach

In a REST API, you typically have multiple endpoints for different types of data:

  1. Fetching User Info:
    • Endpoint: <span class="pink">/users/1</span>
    • Response:
	
{
  "id": "1",
  "name": "Gaurav Sharma",
  "email": "gaurav.sharma@example.com",
  "age": 24,
  "address": "123 MG Road, Bangalore",  // Extra data not needed
  "phone": "9523849635"                // Extra data not needed
}

Problem: This request gives you name, email, and age, but also includes address and phone number, which we don't need. This is over-fetching.

  1. Fetching User Posts:
    • Endpoint: <span class="pink">/users/1/posts</span>
    • Response:
	
[
  {
    "id": "101",
    "title": "My First Post",
    "content": "Hello everyone!"
  },
  {
    "id": "102",
    "title": "Beautiful Sunset",
    "content": "Watching the sunset at Marine Drive"
  }
]

  1. Fetching User Friends:
    • Endpoint: <span class="pink">/users/1/friends</span>
    • Response:
	
[
  {
    "id": "2",
    "name": "Priya Singh"
  },
  {
    "id": "3",
    "name": "Ravi Kumar"
  }
]                                                                                                               

GraphQL Approach

With GraphQL, you can fetch all the required data in a single query:

Query Structure:

  • You start with the main type <span class="pink">user</span> and provide an <span class="pink">id</span> to identify which user you want.
  • Inside the <span class="pink">user</span> type, you request specific fields: <span class="pink">name</span>, <span class="pink">email</span>, and <span class="pink">age</span>.
  • For related data, you include nested queries for <span class="pink">posts</span> and <span class="pink">friends</span>.
	
{
  user(id: "1") {
    name
    email
    age
    posts {
      title
      content
    }
    friends {
      name
    }
  }
}

  • Single Request: Instead of making three separate requests, you make one request to the <span class="pink">/graphql</span> endpoint.
  • Exact Data: You specify exactly what fields you need for the user, posts, and friends. The server responds with only the data you requested:
  • Server Response:
    • The server processes this query, fetching only the required data from the database.
    • It returns a JSON object containing just the requested fields.
	
{
  "data": {
    "user": {
      "name": "Gaurav Sharma",
      "email": "1809157@sbsstc.ac.in",
      "age": 24,
      "posts": [
        {
          "title": "My First Post",
          "content": "Hello everyone!"
        },
        {
          "title": "Beautiful Sunset",
          "content": "Watching the sunset at Marine Drive"
        }
      ],
      "friends": [
        {
          "name": "Priya Singh"
        },
        {
          "name": "Ravi Kumar"
        }
      ]
    }
  }
}

Efficiency:

  • No Over-Fetching: You only get <span class="pink">name</span>, <span class="pink">email</span>, and <span class="pink">age</span> for the user, <span class="pink">title</span> and <span class="pink">content</span> for posts, and <span class="pink">name</span> for friends. There's no extra data.
  • No Under-Fetching: All needed data is retrieved in one request, eliminating the need for multiple API calls.

Over-Fetching and Under-Fetching in Detail with Rest

Over-Fetching

  • Definition: Over-fetching happens when an API returns more data than what is actually needed for a specific request.
  • Example: In the REST example, when fetching user info from <span class="pink">/users/1</span>, the response includes <span class="pink">address</span> and <span class="pink">phone</span> fields, even though the client only needs <span class="pink">name</span>, <span class="pink">email</span>, and <span class="pink">age</span>.
  • Problems:
    • Wastes bandwidth and resources.
    • Slows down the client as it has to process unnecessary data.

Under-Fetching

  • Definition: Under-fetching occurs when an API returns insufficient data, forcing the client to make additional requests to get all the necessary information.
  • Example: In the REST example, to display a complete user profile, you need to call <span class="pink">/users/1</span>, <span class="pink">/users/1/posts</span>, and <span class="pink">/users/1/friends</span>.
  • Problems:
    • Increases the number of requests.
    • Can lead to slower performance and more complex client-side logic to handle multiple responses.

Use Cases:

Use REST when:

  1. Your Data Needs are Simple: If you're building an application where data fetching is straightforward and follows a typical client-server approach, REST is a good choice. For example, fetching a user's profile or a list of products.
  2. Caching Matters: If you need to cache responses for better performance, REST is easier to work with. It has built-in mechanisms for caching, which can help reduce server load and improve response times.
  3. You Have an Existing REST Ecosystem: If your team already has experience with building and consuming RESTful APIs, it might be easier to stick with what you know. This can save time and effort in learning a new technology like GraphQL.
  4. Statelessness is Important: REST APIs are designed to be stateless, meaning each request contains all the information needed to fulfill it. This can simplify server-side logic and make scaling easier.

Use GraphQL when:

  • Your Data Needs are Complex: If your application involves fetching data from multiple sources or dealing with complex data structures, GraphQL shines. It allows clients to request exactly the data they need in a single query, reducing over-fetching or under-fetching of data.
  • Efficient Data Fetching Matters: GraphQL optimizes data fetching by allowing clients to specify exactly what they need. This can lead to smaller payloads and faster response times, especially for clients with limited bandwidth or slow connections.
  • You Value Rapid Development: GraphQL's self-documenting nature and flexible schema make it great for rapid development. It encourages collaboration between frontend and backend teams and allows for easy iteration without breaking changes.

Chapter 4:Getting Started with GraphQL and Spring Boot

Goal:

We'll explore building a GraphQL server using GraphQL and Spring Boot. We'll define a <span class="pink">User</span> class and a <span class="pink">CustomerOrder</span> class, establishing a one-to-many relationship where each user can create multiple orders. User and order details will be stored in separate database tables, ensuring organized data management and enhancing interaction efficiency with clients through our GraphQL API. We'll define schemas, execute queries, and mutations to facilitate seamless data retrieval and manipulation operations within this relational framework.

4.1:

Setting Up the Project

Using Spring Initializr

  1. Open Spring Initializr:Go to Spring Initializr.
  2. Configure Project:
    • Project Type: Maven Project
    • Language: Java
    • Spring Boot Version: Select a stable version (3.x.x or later, as it requires JDK 17 or higher).
  3. Add Dependencies:
    • Spring for GraphQL: Enables support for building GraphQL APIs with Spring Boot.
    • Spring Web: Provides necessary tools to build web applications and RESTful services with Spring.
    • Lombok: Simplifies Java code by automatically generating boilerplate code like getters, setters, and constructors.
    • PostgreSQL Driver: Allows the application to connect to and interact with a PostgreSQL database.
    • Spring Data JPA: Facilitates data access and manipulation with JPA (Java Persistence API) using Spring.
  4. Generate the Project:Click "Generate" to download the project as a ZIP file.
  5. Extract and Open:Extract the ZIP file and open the project in your preferred IDE (e.g., IntelliJ IDEA, Eclipse).

4.2:

Configuring the Project and Coding Part

Database and additional  Configuration:

application.properties

	
spring.application.name=GraphQLWithSpring
spring.datasource.username=postgres
spring.datasource.password=Lumia@540
spring.datasource.url=jdbc:postgresql://localhost:5432/graphql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

Define Entities:

Create your entity classes to represent the data model.

User.java

	
package com.unlogged.entity;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int userId;
    private String name;
    private String email;
    private String phone;
    private String password;
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List <CustomerOrder> orders = new ArrayList<>();
}

Explanation for the above code: The <span class="pink">User</span> class stores information about users, including their name, email, phone number, and password, and also keeps track of their orders using a list.

CustomerOrder.java

	
package com.unlogged.entity;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CustomerOrder {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int orderId;
    private String orderDetail;
    private int price;
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

Explanation for the above code:

The <span class="pink">CustomerOrder</span> class is an entity in the database that represents an order. It includes fields such as orderId, orderDetail, and price, and establishes a many-to-one relationship with the <span class="pink">User</span> entity using JPA annotations for persistence management.

Repository Interfaces:Create interfaces to handle database operations.

CustomerOrderRepo.java

	
package com.unlogged.repo;

import com.unlogged.entity.CustomerOrder;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerOrderRepo extends JpaRepository<CustomerOrder, Integer> {
}

Explanation for the above code:The <span class="pink">CustomerOrderRepo</span> interface extends JpaRepository to provide database access methods for the CustomerOrder entity using Spring Data JPA.

UserRepo.java

	
package com.unlogged.repo;

import com.unlogged.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepo extends JpaRepository<User, Integer> {
}

Explanation for the above code:The <span class="pink">UserRepo</span> interface extends JpaRepository to facilitate database operations for the <span class="pink">User</span> entity using Spring Data JPA.

Defining Service Layers:

OrderService.java

	
package com.unlogged.service;

import com.unlogged.entity.CustomerOrder;
import com.unlogged.repo.CustomerOrderRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class OrderService {

    private final CustomerOrderRepo orderRepo;

    @Autowired
    public OrderService(CustomerOrderRepo orderRepo) {
        this.orderRepo = orderRepo;
    }

    // Create a new order
    public CustomerOrder createOrder(CustomerOrder order) {
        return orderRepo.save(order);
    }

    // Read an order by ID
    public Optional<CustomerOrder> getOrderById(int orderId) {
        return orderRepo.findById(orderId);
    }

    // Read all orders
    public List<CustomerOrder> getAllOrders() {
        return orderRepo.findAll();
    }

    // Update an order
    public CustomerOrder updateOrder(CustomerOrder order) {
        if (orderRepo.existsById(order.getOrderId())) {
            return orderRepo.save(order);
        } else {
            throw new IllegalArgumentException("Order not found with id: " + order.getOrderId());
        }
    }

    // Delete an order by ID
    public void deleteOrderById(int orderId) {
        if (orderRepo.existsById(orderId)) {
            orderRepo.deleteById(orderId);
        } else {
            throw new IllegalArgumentException("Order not found with id: " + orderId);
        }
    }
}

Explanation for the above code:The <span class="pink">OrderService</span> class handles operations for managing customer orders, including creation, retrieval by ID, listing all orders, updating, and deletion by ID using Spring Data JPA.

UserService.java

	
package com.unlogged.service;

import com.unlogged.entity.User;
import com.unlogged.repo.UserRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class UserService {

    private final UserRepo userRepo;

    @Autowired
    public UserService(UserRepo userRepo) {
        this.userRepo = userRepo;
    }

    // Create a new user
    public User createUser(User user) {
        return userRepo.save(user);
    }

    // Read a user by ID
    public Optional<User> getUserById(int userId) {
        return userRepo.findById(userId);
    }

    // Read all users
    public List<User> getAllUsers() {
        return userRepo.findAll();
    }

    // Update a user
    public User updateUser(User user) {
        if (userRepo.existsById(user.getUserId())) {
            return userRepo.save(user);
        } else {
            throw new IllegalArgumentException("User not found with id: " + user.getUserId());
        }
    }

    // Delete a user by ID
    public void deleteUserById(int userId) {
        if (userRepo.existsById(userId)) {
            userRepo.deleteById(userId);
        } else {
            throw new IllegalArgumentException("User not found with id: " + userId);
        }
    }
}

Explanation for the above code:The <span class="pink">UserService</span> class provides methods to manage user data, including creation, retrieval by ID, listing all users, updating, and deletion by ID using Spring Data JPA.

Defining GraphQL Schema:After creating the project, you should see a folder named <span class="pink">graphql</span> in the resources section. If it's not there, go ahead and create a folder named <span class="pink">graphql</span> inside the resources folder. Next, create a file named <span class="pink">schema.graphqls</span> (make sure to use this exact name). We'll be defining all our schema, query, and mutation  in this file.

schema.graphqls

	
type User{
    userId:ID!,
    name:String,
    email:String,
    phone:String,
    password:String,
    orders:[CustomerOrder]

}

type CustomerOrder{
    orderId:ID,
    orderDetail:String,
    price:Int,
    user:User
}



type Query{
    getAllUsers:[User]
    getUserById(userId:ID!):User
    getAllOrders:[CustomerOrder]
    getOrderById(orderId:ID!):CustomerOrder
}



type Mutation{
    createUser(name:String,email:String,phone:String,password:String):User
    deleteUserById(userId:ID!):Boolean
    createOrder(orderDetail:String,price:Int,userId:ID!):CustomerOrder
    deleteOrderById(orderId:ID!):Boolean
}

Explanation for the above code:

This GraphQL schema defines how to manage users and their orders. It includes two main types: <span class="pink">User</span>, with fields like <span class="pink">userId</span>, <span class="pink">name</span>, <span class="pink">email</span>, <span class="pink">phone</span>, <span class="pink">password</span>, and a list of orders; and <span class="pink">CustomerOrder</span>, with fields like <span class="pink">orderId</span>, <span class="pink">orderDetail</span>, <span class="pink">price</span>, and the user who made the order. In GraphQL, a "type" defines the structure of data, ensuring consistency. Think of types as blueprints that describe what kind of data you have and how it is organized.

Types in Our Schema

  • User Type:
    • Represents a user in the system.
    • Fields:
      • <span class="pink">userId</span>: A unique identifier for the user.
      • <span class="pink">name</span>: The user's name.
      • <span class="pink">email</span>: The user's email address.
      • <span class="pink">phone</span>: The user's phone number.
      • <span class="pink">password</span>: The user's password.
      • <span class="pink">orders</span>: A list of orders made by the user.
  • CustomerOrder Type:
    • Represents an order placed by a user.
    • Fields:
      • <span class="pink">orderId</span>: A unique identifier for the order.
      • <span class="pink">orderDetail</span>: Details about the order.
      • <span class="pink">price</span>: The price of the order.
      • <span class="pink">user</span>: The user who placed the order.

Queries

Queries are used to fetch data from the server. They define the read operations in GraphQL:

  • getAllUsers: Retrieves a list of all users.
  • getUserById: Retrieves a specific user by their <span class="pink">userId</span>.
  • getAllOrders: Retrieves a list of all orders.
  • getOrderById: Retrieves a specific order by its <span class="pink">orderId</spa>.

Queries allow clients to specify exactly which fields they need, minimizing data transfer and improving efficiency.

Mutations

Mutations are used to modify data on the server. They define the write operations in GraphQL:

  • createUser: Creates a new user with provided details (<span class="pink">name, email, phone, password</span>) and returns the created user.
  • deleteUserById: Deletes a user by their <span class="pink">userId</span> and returns a boolean indicating whether the operation was successful.
  • createOrder: Creates a new order with provided details (<span class="pink">orderDetail, price, userId</span>) and returns the created order.
  • deleteOrderById: Deletes an order by its <span class="pink">orderId</span> and returns a boolean indicating whether the operation was successful.

Mutations allow clients to perform actions that change the state of data, such as creating or deleting records.

Defining Controller classes :

Now, we will create a controller class that will map to the predefined queries and mutations in the <span class="pink">schema.graphqls</span> file.

UserController.java

	
package com.unlogged.controller;

import com.unlogged.entity.User;
import com.unlogged.service.UserService;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

import java.util.List;
import java.util.Optional;

@Controller
public class UserController {
    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }


//create user
    @MutationMapping
 public User createUser(@Argument String name, @Argument String email, @Argument String phone, @Argument String password){
       User user=new User();
       user.setName(name);
       user.setEmail(email);
       user.setPhone(phone);
       user.setPassword(password);
        return userService.createUser(user);
 }


//get all users
@QueryMapping
 public List<User> getAllUsers(){
        return userService.getAllUsers();
 }
 
 
//get user by id
 @QueryMapping
 public Optional<User> getUserById(@Argument int userId){
      return  userService.getUserById(userId);
 }


//delelte user by id
 @MutationMapping
 public Boolean deleteUserById(@Argument int userId){
        return userService.deleteUserById(userId);
 }
}

Explanation for the above code:

The <span class="pink">UserController</span> class handles GraphQL operations for creating, retrieving, listing, and deleting users.

Two annotations frequently utilized in the mentioned controller class are:

  • <span class="pink">@QueryMapping</span>: It defines how to fetch (or get) data from the server. It specifies how clients can request specific information.
  • <span class="pink">@MutationMapping</span>: It defines how to modify (or change) data on the server. It specifies operations like adding, updating, or deleting data.

OrderController.java

	
package com.unlogged.controller;

import com.unlogged.entity.CustomerOrder;
import com.unlogged.entity.User;
import com.unlogged.service.OrderService;
import com.unlogged.service.UserService;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

import java.util.List;
import java.util.Optional;

@Controller
public class OrderController {

    private UserService userService;

    public OrderController(UserService userService, OrderService orderService) {
        this.userService = userService;
        this.orderService = orderService;
    }

    private OrderService orderService;


    //create order
    @MutationMapping
    public CustomerOrder createOrder(@Argument String orderDetail, @Argument Integer price, @Argument int userId) {
        Optional<User> user = userService.getUserById(userId);

        CustomerOrder order = new CustomerOrder();
        order.setOrderDetail(orderDetail);
        order.setPrice(price);
        order.setUser(user.get());
        return order;
    }

    //get order by id
    @QueryMapping
    public CustomerOrder getOrderById(@Argument int orderId) {
        return orderService.getOrderById(orderId).get();
    }


    //get all orders
    @QueryMapping
    public List<CustomerOrder> getAllOrders() {
        return orderService.getAllOrders();
    }


//delete order by id
    @MutationMapping
    public boolean deleteOrderById(@Argument int orderId) {
        return orderService.deleteOrderById(orderId);
    }
}

Explanation for the above code:

The <span class="pink">OrderController</span> class handles operations for managing customer orders using GraphQL, such as creating, retrieving by ID, listing all orders, and deleting by ID.

4.3:

Running the Project

Run your application using your IDE or by executing <span class="pink">./mvnw spring-boot:run</span> in the project root directory.

Note: Upon starting the application, you will observe 0 unmapped fields in the console. This is a result of implementing controller methods for all mutations and queries defined in the schema.graphqls file.

Chapter 5: API Testing with GraphiQL

5.1 :

Understanding GraphiQL

GraphiQL is like a playground where developers can interact with GraphQL APIs directly in their web browser.

  • Explore and Understand: It provides an easy-to-use interface to explore the structure of a GraphQL API. As you type, it shows you what data you can request (like fields and their types), which helps in understanding how the API works.
  • Write and Test Queries: You can write GraphQL queries and see the results instantly. It’s like writing a question and getting an immediate answer. This makes it super handy for testing and checking if your queries work as expected.
  • Spot Mistakes Easily: GraphiQL highlights errors in your queries, like typos or missing information. This helps you fix issues quickly without guessing.
  • History and Autocomplete: It remembers the queries you’ve run before, so you can go back to them easily. It also suggests what you can type next, which saves time and reduces errors.

💡 To enable GraphiQL, you just need to add <span class="pink">spring.graphql.graphiql.enabled=true</span> in your <span class="pink">application.properties</span> file.

To test our GraphQL API, we can use GraphiQL. By accessing the GraphQL playground at <span class="pink">http://localhost:8080/graphiql</span> in our web browser, we can interact with the API directly. Please adjust the port number according to your project setup (mine is running on port 8080).

It should look like this :

Here, you'll find all the queries and mutations defined in the <span class="pink">schema.graphqls</span> file listed, enabling you to directly write and execute your GraphQL queries and mutations.

our tables in postgres db initally before executing any mutation :

customer_order table

users table

5.2: Example demonstrating a Mutation

Creating an User

Below is an example of a GraphQL mutation to create a new user with the details of an individual named Gaurav Sharma:

	
mutation {
  createUser(name: "Gaurav Sharma", email: "1809157@sbsstc.ac.in", phone: "+919523849635", password: "pass123") {
    userId
    name
    email
    phone
  }
}

The first field in the graphql is where we write the query and second field is where we get our desired response.

Explanation:

  • mutation { ... }: Indicates that this is a GraphQL mutation operation.
  • createUser(name: "Gaurav Sharma", email: "1809157@sbsstc.ac.in", phone: "+919523849635", password: "pass123"): Calls the <span class="pink">createUser</span> mutation, passing in the user's name, email, phone, and password.
  • { userId, name, email, phone }: Specifies the fields to be returned in the response. In this case, after creating the user, the mutation returns the <span class="pink">userId</span>, <span class="pink">name</span>, <span class="pink">email</span>, and <span class="pink">phone</span> of the newly created user.
  • Note: We have not requested the password in the response, so it is not included.

This mutation efficiently creates a new user and retrieves essential details, providing immediate confirmation of the new user's data.

Demo:

checking if the record is inserted in table after executing mutation in GraphiQL (can be accessed at <span class="pink">http://localhost:8080/graphiql)</span> :

One can observe that a new entry is created in the users table.

5.3:

Crucial: Sequence of how the Mutation is Executed in GraphiQL

  1. GraphQL Schema (<span class="pink">schema.graphqls</span>):
    • You define mutations like <span class="pink">createUser</span> in <span class="pink">schema.graphqls</span>, specifying input parameters (<span class="pink">name, email, phone, password</span>) and the expected return type (<span class="pink">User</span>).
  2. GraphiQL (GraphQL Playground):
    • You compose and execute a mutation query. For example:
	
mutation {
  createUser(name: "Gaurav Sharma", email: "1809157@sbsstc.ac.in", phone: "+919523849635", password: "pass123") {
    userId
    name
    email
    phone
  }
}

  • This mutation request is sent to your GraphQL server (Spring Boot application).
  1. Spring Boot Application - GraphQL Controller (<span class="pink">UserController</span>):
    • The GraphQL controller (<span class="pink">UserController</span>) receives the mutation request.
    • It maps the incoming mutation (<span class="pink">createUser</span>) based on the <span class="pink">@MutationMapping</span> annotation to the corresponding method (<span class="pink">createUser</span>) in <span class="pink">UserController</span>.
  2. UserController:
    • The <span class="pink">createUser</span> method in <span class="pink">UserController</span> executes upon receiving the mutation request.
    • It retrieves parameters (<span class="pink">name, email, phone, password</span>) from the mutation request using annotations like <span class="pink">@Argument</span>.
    • It creates a new <span class="pink">User</span> object with the received parameters.
  3. Service Layer (<span class="pink">UserService</span>):
    • The <span class="pink">UserController</span> delegates the task of creating a user to the <span class="pink">UserService</span>.
    • The <span class="pink">UserService</span> performs business logic, such as validating data, before interacting with the data access layer (typically a repository or database).
  4. Data Access Layer (Repository or Database):
    • The <span class="pink">UserService</span> saves the new <span class="pink">User</span> object to the database or retrieves existing data as required.
  5. Response Flow:
    • The created <span class="pink">User</span> object or any defined return values (<span class="pink">userId, name, email, phone</span>) are returned back through the layers.
    • The GraphQL controller formats the response according to the GraphQL schema (<span class="pink">schema.graphqls</span>) and sends it back to the client (GraphiQL).
Summary:

When you execute a mutation in GraphiQL:

  • GraphQL Schema defines the structure and operations.
  • GraphiQL sends the mutation request to the GraphQL server.
  • GraphQL Controller (<span class="pink">UserController</span>) maps and processes the mutation request.
  • Service Layer (<span class="pink">UserService</span>) handles business logic and interacts with the data layer.
  • Data Access Layer manages storage and retrieval of data.
  • Response flows back through the layers, formatted as per the GraphQL schema, to GraphiQL where you can view the results.

5.4→Example of Query Execution

Executing a detailed query:

This GraphQL query retrieves a list of all orders, including each order's ID, details, and price, along with the associated user's ID, name, email, and phone number. This showcases the impressive capability of GraphQL.

	
query {
  getAllOrders {
    orderId
    orderDetail
    price
    user {
      userId
      name
      email
      phone
    }
  }
}

Response:

	
{
  "data": {
    "getAllOrders": [
      {
        "orderId": "1",
        "orderDetail": "New Laptop",
        "price": 1200,
        "user": {
          "userId": "3",
          "name": "Gaurav Sharma",
          "email": "1809157@sbsstc.ac.in",
          "phone": "+919523849635"
        }
      },
      {
        "orderId": "2",
        "orderDetail": "New Shoe",
        "price": 12,
        "user": {
          "userId": "3",
          "name": "Gaurav Sharma",
          "email": "1809157@sbsstc.ac.in",
          "phone": "+919523849635"
        }
      },
      {
        "orderId": "3",
        "orderDetail": "New Bike",
        "price": 12000,
        "user": {
          "userId": "3",
          "name": "Gaurav Sharma",
          "email": "1809157@sbsstc.ac.in",
          "phone": "+919523849635"
        }
      },
      {
        "orderId": "4",
        "orderDetail": "New Car",
        "price": 120000,
        "user": {
          "userId": "5",
          "name": "Sameer",
          "email": "Sameer@123.ac.in",
          "phone": "+91949635"
        }
      }
    ]
  }
}

Demo for the same:

Chapter 6: Shortcomings of GraphQL

Complexity in Query Execution

GraphQL queries can sometimes be very detailed and complicated, requiring the server to do a lot of work to gather the requested data. This can slow down performance, especially with very nested queries.

Over-fetching and Under-fetching

Even though GraphQL aims to prevent over-fetching (getting too much data) and under-fetching (not getting enough data), poorly designed queries can still cause these problems. Over-fetching means you ask for more information than needed, and under-fetching means you don't get everything you need, which might force you to make additional requests.

N+1 Query Problem

GraphQL can sometimes cause what's known as the N+1 query problem. This happens when a single query triggers many small queries to the database, which can be inefficient and slow.

Learning Curve and Tooling

Learning GraphQL can be challenging. Developers need to understand new concepts like schema design and resolvers, which can be more complicated compared to REST APIs.

Caching Complexity

Caching is harder in GraphQL than in REST APIs. REST APIs cache data based on endpoints, but GraphQL's flexible queries require more advanced caching strategies.

Authorization and Security

Securing a GraphQL API can be complex. Each field can potentially be an entry point for an attack, so protecting data at this detailed level requires careful planning and implementation.

Error Handling

Handling errors in GraphQL is different from REST. Instead of using HTTP status codes, GraphQL includes errors in the response body. This requires special logic to interpret and handle different types of errors.

Versioning

Versioning APIs in GraphQL is not as straightforward as in REST. REST can use simple versioned endpoints like <span class="pink">/v1/endpoint</span>, but GraphQL needs a more nuanced approach to handle changes without breaking existing clients.

💡 GraphQL is a broad and extensive topic that cannot be fully covered in a single article. If you find it intriguing, you are encouraged to explore further at your own pace.

Cheers!

Happy Coding.

Gaurav Sharma
June 17, 2024
Use Unlogged to
mock instantly
record and replay methods
mock instantly
Install Plugin