Intro to GraphQL with Spring Boot

Amit Chavda
Simform Engineering
9 min readAug 3, 2023

Learn how to use GraphQL with Spring Boot to make the API development process easy and more developer friendly.

In this blog, we’ll explore how GraphQL and Spring Boot collaborate seamlessly. They make it easy and enjoyable for developers to create APIs with simplicity and friendliness. Spring Boot is renowned for its rapid setup and configuration capabilities, making it a popular choice for deploying backend applications. Meanwhile, GraphQL is a cutting-edge technology crafted to revolutionize APIs with its speed, adaptability, and developer-centric design. Together, they form a powerful combination for modern API development.

GraphQL boasts a smart query language and runtime crafted by Facebook. This innovation tackles the drawbacks of regular REST APIs. With GraphQL, we can pinpoint the exact data we need, avoiding the hassles of getting too much or too little. It’s all about efficiency and delivering just what’s necessary.

In our Spring Boot project, we’ll harness the power of GraphQL. By combining it with Spring Boot, we’ll create an intuitive and seamless integration. We’ll begin by crafting a GraphQL schema — a masterpiece that defines data types and governs queries and mutations. We’ll use GraphQL Schema Definition Language (SDL) to orchestrate this process.

What is GraphQL?

GraphQL is an innovative open-source data query and manipulation language for APIs. It revolutionizes the way developers interact with data, offering a visionary approach. With its query runtime engine, GraphQL allows declarative data fetching, freeing developers from over-fetching data. They can now precisely request what they need from a single API endpoint.

Imagine a developer’s dream come true, where managing multiple API endpoints becomes a thing of the past. GraphQL emerges as the ultimate solution, streamlining data retrieval with efficiency and elegance like never before.

Key terms in GraphQL

Schema: The schema is the core of GraphQL, an essential blueprint that shapes your API’s structure, defines its data types, and showcases its impressive capabilities. It acts as a friendly agreement between the client and server, laying out which data can be requested and what will be provided in return.

Imagine the schema as a detailed roadmap that guides developers and clients through the API landscape. It clarifies what data can be accessed and how to make it a breeze to interact with the API efficiently.

Schema {
query: Query
mutation: Mutation
}

Type: At the core of GraphQL’s charm is its dedication to precision and clarity through strict typing. With a belief that “types matter,” GraphQL provides developers with an elegant and robust foundation for API development.

Strict typing in GraphQL ensures that data is well-defined and consistent, making it easier for developers to understand and work with the API.

type User {
name:String!
email:String!
}

Field: In GraphQL, a field is a crucial element found within a Type. It represents a specific piece of data that the Type holds, just like properties in a Java class. For example, in a “User” Type, fields like “name” and “email” define the user’s identity. Each field acts like a brushstroke, painting a clear picture of the data it represents within the GraphQL universe.

Query: In GraphQL, a query is a powerful tool that fetches data from the API endpoint. It acts as a gateway to access information, like a key unlocking the treasures of the GraphQL universe. But remember, queries are meant only for reading data, not for updating or creating it. They are like reading glasses, allowing you to explore and retrieve the data you need with ease and clarity.

type Query {
findAllUser:[User]
findUserById(id:ID):User
}

Here, findAllUser and findUserById are queries we defined to fetch user data.

Note: We cannot update or insert data using queries.

Mutation: In GraphQL, a mutation is a specialized tool designed for data manipulation. Unlike queries, which are used for reading data, mutations take on the role of guardians of change. They provide a controlled and elegant way to perform insert or update operations within the GraphQL universe.

type Mutation {
createUser(user:User): Void
updateUser(user:Uder):User
}

Scalar: In GraphQL, a scalar is a fundamental data type representing a single value. Scalars are the building blocks used to define the shape and structure of the data within the GraphQL schema. Unlike object types that can have multiple fields, scalars can hold only one value at a time.

GraphQL comes with a set of default scalar types, including Int, Float, String, Boolean, and ID.

Let’s create a Spring Boot application and dive into the mesmerizing world of GraphQL

Set up the Spring Boot Project using your preferred IDE and build tool, such as Maven, and create a new Spring Boot project. Here, we’ll create a simple Todo app to demonstrate how GraphQL works.

Add the necessary dependencies for Spring Boot and GraphQL to your project’s configuration file.

<dependency> 
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>

Create Todo and TodoItem entities and necessary JPA repositories. You can see the entities I have created here.

Create schema.graphqls resource in the resources package which will contain GraphQL queries.

Figure 1 GraphQL schema file

Since GraphQL doesn’t have native support for Java LocalDate and LocalDateTime, we’ll create custom scalar types to handle these types in our schema. You can find the implementation of these custom scalar types here.

Define Todo and TodoItem types with the following fields:

scalar LocalDate
scalar LocalDateTime

type Todo{
id:ID
title:String
markAsComplete:Boolean
completionDate:LocalDate
items:[TodoItem]
}

type TodoItem{
id:ID
notes:String
dueDate:LocalDate
markAsComplete:Boolean
completionDate:LocalDate
createdAt:LocalDateTime
updatedAt:LocalDateTime
}

Define the GraphQL schema and query to get all Todo items.

In this step, we’ll create a simple GraphQL schema with a single query to fetch all Todo items.

schema{
query:Query
}

type Query{
getAllTodo:[Todo]
}

Now let’s create TodoController and create a method that returns a list of Todo and annotate it @QueryMapping so that this getAllTodo query will be mapped. You can find the full code here.

    @QueryMapping
public List<TodoDto> getAllTodo() {
return todoService.getAllTodos();
}

Additionally, you can enable GraphQL with the following property, a powerful GraphQL interface to easily develop and test GraphQL APIs. You will be able to access the interface on http://localhost:[port]/graphiql endpoint.

spring:
graphql:
graphiql:
enabled: true
path: /graphiql

When you hit http://localhost:[port]/graphiql , you’ll see the following interface in your browser:

Figure 2 GraphiQL or GraphQL Playground

Now, let’s write the query to fetch data from the database. Some data has already been pushed into the database. After this, we’ll learn how to create mutations and use them to add/update data in the database.

Figure 3 GraphQL Query example requesting all fields in GraphQL Playground

As you can see, we have fetched all the fields available in the query we created earlier. However, the real power of GraphQL lies in its flexibility to access only the fields that are required. This ability allows for more efficient data retrieval, reducing unnecessary overhead and optimizing performance.

Figure 4 GraphQL Query example requesting fewer fields in GraphQL Playground

In the snapshot above, I have removed the id, createdAt, and updatedAt fields, and upon execution, I received only the specified fields and values, not all of them.

Now, let’s create a mutation to save the Todo. In GraphQL, we cannot pass a type object as a request body. Therefore, we need to define input types separately from the types we used for the query earlier. We will define the input types as follows:

# Input types
input TodoInput{
title:String
markAsComplete:Boolean
items:[TodoItemInput]
}
input TodoItemInput{
id:ID
notes:String
dueDate:LocalDate
markAsComplete:Boolean
}

We’ll add mutation in the schema as well.

schema{
mutation:Mutation
query:Query
}

type Mutation{
createTodo(todo:TodoInput):Todo
}

Create a method in TodoController that saves Todo in the database. To map this mutation with our logic, we’ll use the @MutationMapping annotation, and GraphQL will identify the parameters using the @Argument annotation. See the full implementation here.

 @MutationMapping
public TodoDto createTodo(@Argument(value = "todo") TodoDto todoDto) {
return todoService.addTodo(todoDto);
}

Open the GraphQL interface and hit the play button with the following request body, and we’ll get the response as follows:

Figure 5 GraphQL Mutation example in GraphQL Playground

The field mentioned outside the createTodo() method determines the response structure. Whatever fields we include here will be returned in the response. This allows us to customize the data returned as per the requirements.

Advantages

Flexible Queries: With GraphQL, we can request exactly the data we need and nothing more. This eliminates issues related to over-fetching or under-fetching data, reducing the payload size and optimizing network performance.

Single Endpoint: Unlike traditional REST APIs that often require multiple endpoints for different resources, GraphQL provides a single endpoint for all data operations. This simplifies the API structure and reduces the number of network requests needed.

Strongly Typed Schema: GraphQL uses a strongly typed schema to define the data structure. This schema acts as a contract between the client and server, providing clear documentation of the available data and operations.

Introspection: GraphQL provides introspection capabilities, allowing us to query the schema itself. This feature enables powerful client tools, such as GraphQL Playground or GraphiQL, to explore the API and understand its capabilities.

Rapid Development and Iteration: GraphQL allows front-end and back-end teams to work independently, as long as they follow the agreed-upon schema. This promotes faster development and iteration, as changes to the schema do not necessarily break the client applications.

Version Free API: Unlike REST APIs, which often require versioning to manage backward compatibility, GraphQL allows for seamless backward-compatible schema changes without breaking existing clients. Fields can be added, deprecated, or modified without changing the overall structure.

Reduced API Churn: Since clients can request precisely the data they need, they are less affected by changes in the backend data structures. This reduces API churn and the need for constant adjustments in the client codebase.

Disadvantages

Complexity: GraphQL introduces additional complexity compared to traditional REST APIs, especially for developers who are new to the technology. Setting up a GraphQL server and defining a schema properly requires a good understanding of GraphQL concepts and best practices.

Caching: GraphQL does not inherently support caching like traditional REST APIs, where you can use HTTP caching mechanisms. Implementing caching in GraphQL can be more challenging, and custom caching solutions are often required.

Increased Payloads: While GraphQL offers the flexibility to request only the required data, it can lead to over-fetching or under-fetching of data. In some cases, GraphQL responses may be larger than necessary, causing increased payload sizes and potentially impacting network performance.

File Uploads: Handling file uploads in GraphQL can be less straightforward compared to traditional REST APIs, where it’s common to use multipart/form-data. GraphQL doesn't natively support file uploads, requiring additional configuration or extensions like GraphQL multipart requests.

Learning Curve: Adopting GraphQL may require a learning curve for developers who are already familiar with REST APIs. Understanding GraphQL schemas, queries, mutations, and subscriptions can take time to grasp fully.

Security Concerns: GraphQL’s flexibility can introduce potential security risks if not properly implemented. For instance, malicious queries could lead to data exposure or denial-of-service attacks. Implementing rate limiting and access controls is critical to mitigate these risks.

Key Takeaways

With GraphQL, we can request only the data we need, avoiding over-fetching and under-fetching. This optimization results in reduced data transfer and improved network performance.

The combination of GraphQL and Spring Boot empowers developers to build efficient APIs that meet the demands of modern applications. This approach offers a robust and future-proof solution for creating backend services that cater to evolving needs.

To stay in the loop with the coolest and latest trends in the development world, follow us on Medium, LinkedIn and Twitter.

--

--

Amit Chavda
Simform Engineering

Java | Spring Boot | AWS | Docker | Jenkins | PostgreSQL