DEV Community

Chanuka Dinuwan
Chanuka Dinuwan

Posted on

Building a GraphQL API with Spring Boot: A Complete Guide

Image description

In modern API development, Eve``ryone focuses on flexibility and efficiency. Traditional REST APIs often lead to over- or under-fetching of data, especially in complex systems with deeply nested relationships. Graphql, a query language developed by Facebook, addresses these issues by allowing clients to request the exact data they need.

When combined with Spring Boot, a powerful Java-based framework for building Microservices, GraphQL becomes a robust solution for building flexible, scalable APIs. This blog discusses how to integrate GraphQL with Spring Boot, allowing you to build APIs that are not only performant and type-safe but also developer-friendly and adaptable to changing client requirements.

GraphQL Documentation: https://graphql.org

GraphQL with Spring Boot Documentation: https://spring.io/guides/gs/graphql-server

GraphQL vs Rest

Rest has been the standard architectural style for building APIs for years now, GraphQL offers a modern alternative designed for flexibility and efficiency in data retrieval. The table below summarizes the key differences between the two approaches:

Image description

Set up Spring Boot Application

We need to use a GraphQL maven or gradle dependency to implement the Spring boot application with GraphQL.

`
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
`

We need to create a simple Spring Boot application to try this or you can clone it here:

https://github.com/cdin8619-0/graphql-springboot.git

Project Structure

Let’s dive into the main component of our application:

  • Entity classes
  • Repository interfaces
  • Service classes
  • Controllers with GraphQL annotations
  • GraphQL schema definition
  • Controller implementation

Controller implementation

`
@Controller
public class ProductController {

private ProductService productService;

@Autowired
public ProductController(ProductService productService) {
    this.productService = productService;
}

@QueryMapping
public List<Product> getProducts() {
    return productService.getAllProducts();
}

@QueryMapping
public List<Product> getProductsByCategory(@PathVariable String category) {
    return productService.getProductsByCategory(category);
}

@MutationMapping
public Product updateStock(@Argument int productId, @Argument int stock) {
    return productService.updateStock(productId, stock);
}

@MutationMapping
public Product receiveNewShipment(@Argument int productId, @Argument int quantity) {
    return productService.receiveNewShipment(productId, quantity);
}
Enter fullscreen mode Exit fullscreen mode

}
`

The @QueryMapping and @MutationMapping annotations in Spring for GraphQL simplify linking methods to GraphQL query and mutation fields. They are concise alternatives to @SchemaMapping:

  • @QueryMapping: Maps a method to a GraphQL query field, typically using the method name.
  • @MutationMapping: Maps a method to a GraphQL mutation field for handling data changes.

Service Implementation

This is the place where we write logic. This is the same as the normal Spring Boot applications.

`
@Service
public class ProductService {

@Autowired
private ProductRepository productRepository;

public List<Product> getAllProducts() {
    return productRepository.findAll();
}

public List<Product> getProductsByCategory(String category) {
    return productRepository.findByCategory(category);
}

public Product updateStock(int id , int stock) {
    Product existingProduct = productRepository.findById(id).orElseThrow(() -> new RuntimeException("Product not found"));

    existingProduct.setStock(stock);
    return productRepository.save(existingProduct);
}

public Product receiveNewShipment(int id , int quantity) {
    Product existingProduct = productRepository.findById(id).orElseThrow(() -> new RuntimeException("Product not found"));

    existingProduct.setStock(existingProduct.getStock() + quantity);
    return productRepository.save(existingProduct);
}
Enter fullscreen mode Exit fullscreen mode

}
`

GraphQL Schema Definition

Our application needs to define the schema that describes our data model and operations. Create a file named schema.graphqls in the src/main/resources/graphql directory:

`
type Product {
id: ID,
name: String,
category: String,
price: Float,
stock: Int
}

type Query {
getProducts: [Product]
getProductsByCategory(category: String): [Product]
}

type Mutation {
updateStock(productId: ID, stock: Int): Product
receiveNewShipment(productId: ID, quantity: Int): Product
}
`

Entity Class and Repository Implementation

`
@Entity
@data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

private String name;
private String category;
private float price;
private int stock;
Enter fullscreen mode Exit fullscreen mode

}
`

`
@Repository
public interface ProductRepository extends JpaRepository<Product, Integer> {
List<Product> findByCategory(String category);
}
`

I created a data loader class to load initial data to the application.

Add the following properties to application.properties(or application.yml):

`
spring.application.name=graphql-springboot
server.port=9090

Database configuration

spring.datasource.url=jdbc:h2:mem:productdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.datasource.auto-create=true

GraphQL endpoint path (default is /graphql)

spring.graphql.path=/graphql

Enable GraphiQL

spring.graphql.graphiql.enabled=true
spring.graphql.graphiql.path=/graphiql
`

Database configuration may be different for yours than mine. But add the above-mentioned GraphQL configurations to your application.

Testing with GraphiQL

Spring Boot with GraphQL provides an integrated GraphiQL interface for testing GraphQL queries. After running the application, navigate to http://localhost:8080/graphiql to interact with your API. There you can play with application GraphQL APIs.

Image description

Here are some sample queries and mutations:

`
Get All Products
graphqlquery {
getProducts {
id
name
category
price
stock
}
}

Get Products by Category
graphqlquery {
getProductsByCategory(category: "Electronics") {
id
name
price
stock
}
}

Update Product Stock
graphqlmutation {
updateStock(productId: 1, stock: 25) {
id
name
category
stock
}
}

Receive New Shipment
graphqlmutation {
receiveNewShipment(productId: 2, quantity: 10) {
id
name
category
stock
}
}
`

You can also use a Postman or a similar tool to test your GraphQL application.

Image description

Error Handing in GraphQL

GraphQL has a different approach to error handling compared to REST. Let’s enhance our application with proper error handling.

`
package com.example.graphqlspringboot.exception;

import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.stereotype.Component;

@Component
public class GraphQLExceptionHandler extends DataFetcherExceptionResolverAdapter {

@Override
protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
    return GraphqlErrorBuilder.newError()
            .message(ex.getMessage())
            .path(env.getExecutionStepInfo().getPath())
            .location(env.getField().getSourceLocation())
            .build();
}
Enter fullscreen mode Exit fullscreen mode

}
`

Advanced Features

DataLoaders for Batch Loading

You might face the “N+1 queries” problem when dealing with complex data relationships. GraphQL DataLoaders help solve this issue by batching and caching requests:

`
@Component
public class ProductDataLoader {

private final ProductRepository productRepository;

public ProductDataLoader(ProductRepository productRepository) {
    this.productRepository = productRepository;
}

@SchemaMapping(typeName = "Query", field = "productsByIds")
public List<Product> getProductsByIds(@Argument List<Integer> ids) {
    return productRepository.findAllById(ids);
}
Enter fullscreen mode Exit fullscreen mode

}
`

Subscriptions for Real-time Update

GraphQL also supports subscriptions for real-time updates. Let’s add a simple stock level subscription:

`

// In GraphQL schema
type Subscription {
stockLevelChanged: Product
}

// In Java code
@SubscriptionMapping
public Publisher stockLevelChanged() {
return productPublisher;
}

`

Security Considerations

When implementing GraphQL APIs, consider these security aspects:

  • Query Complexity Analysis: Prevent resource-exhausting queries
  • Rate Limiting: Control the frequency of requests
  • Authentication and Authorization: Protect sensitive operations
  • Input Validation: Validate all client inputs

Performance Optimization

For optimal GraphQL performance:

  • Query Depth Limiting: Prevent deeply nested queries
  • Result Size Limiting: Control response payload sizes
  • Caching: Implement response caching
  • Pagination: Use cursor-based pagination for large result sets

Conclusion

GraphQL with Spring Boot offers a powerful alternative to traditional REST APIs, providing clients with the flexibility to request exactly the data they need. The strong typing, self-documenting nature, and single endpoint approach make it an excellent choice for modern applications with complex data requirements.

By implementing GraphQL in your Spring Boot applications, you can:

  • Reduce over-fetching and under-fetching of data
  • Create better developer experiences with strong typing and tooling
  • Enable clients to evolve independently of the server
  • Simplify API versioning
  • Build more responsive and efficient applications

The example provided in this article demonstrates basic GraphQL concepts with Spring Boot, but you can extend it with more complex features like relationships between entities, pagination, filtering, and advanced security measures.

Remember that GraphQL is not a replacement for REST in all cases — choose the approach that best fits your specific application requirements and constraints.

Happy coding!🚀

Top comments (1)

Collapse
 
karan_patel_d1fea60c1e533 profile image
Karan Patel

I recently made a Commercial grade Project using Spring Boot. Would love your feedback or suggestions on how I can expand and improve it, check out here
linkedin.com/feed/update/urn:li:ac...