DEV Community

Anh Trần Tuấn
Anh Trần Tuấn

Posted on • Originally published at tuanh.net on

Integrate Spring Boot with GraphQL for Highly Flexible APIs

1. Core Concepts in GraphQL and Spring Boot Integration

1.1 What is GraphQL?

GraphQL is a query language for APIs, designed by Facebook, that allows clients to request only the data they need:

  • Unlike REST, where you hit a fixed endpoint for a specific resource, GraphQL lets you request only specific fields within those resources.
  • For example, in a REST API, fetching a User might bring back a complete object, even if you only need the name and email. With GraphQL, the client can request only these two fields, optimizing data transfer.

1.2 Setting Up Spring Boot for GraphQL

Begin by listing the necessary dependencies to get GraphQL running in a Spring Boot application:

Add dependencies in pom.xml:

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>11.1.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>11.1.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

graphql-spring-boot-starter: Enables a GraphQL endpoint in Spring Boot.

graphiql-spring-boot-starter: Provides a graphical interface for testing GraphQL queries in the browser.

Next, configure the GraphiQL endpoint in application.properties:

graphql.graphiql.enabled=true
graphql.graphiql.path=/graphiql
graphql.servlet.mapping=/graphql
Enter fullscreen mode Exit fullscreen mode

This allows you to access GraphiQL at http://localhost:8080/graphiql and test queries directly in the browser.

1.3 Key Components: Schema, Resolvers, and DataFetcher

Schema Definition : This defines the structure of your API and the types of queries you can make. In GraphQL, schemas are typically defined in .graphqls files.

type Book {
    id: ID!
    title: String!
    author: Author!
    genre: Genre
    publicationDate: String
}
type Author {
    id: ID!
    name: String!
    books: [Book]
}
type Query {
    getBooks: [Book]
    getAuthor(id: ID!): Author
}
Enter fullscreen mode Exit fullscreen mode

Here, Book and Author are types, and Query defines the API’s entry points.

Each field type ends with ! if it's mandatory.

Resolvers : These connect the schema to the backend logic. For example, a BookQueryResolver fetches books from the database.

@Component
public class BookQueryResolver implements GraphQLQueryResolver {
    private final BookRepository bookRepository;

    public BookQueryResolver(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public List<Book> getBooks() {
        return bookRepository.findAll();
    }
}
Enter fullscreen mode Exit fullscreen mode

This resolver class fetches all books using BookRepository and is mapped to the getBooks query in the schema.

DataFetcher : Use this interface for more complex data-fetching logic, such as filtering or transformation before returning the data.

DataFetcher<List<Book>> booksDataFetcher = environment -> {
    String genreFilter = environment.getArgument("genre");
    return bookRepository.findByGenre(genreFilter);
};
Enter fullscreen mode Exit fullscreen mode

1.4 Building a Basic Book API

Create an entity class for Book:

@Entity
public class Book {
    @Id @GeneratedValue
    private Long id;
    private String title;
    private String genre;
    private String publicationDate;
    @ManyToOne
    private Author author;

    // Getters and Setters
}
Enter fullscreen mode Exit fullscreen mode

Create a BookRepository using Spring Data JPA:

public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findByGenre(String genre);
}
Enter fullscreen mode Exit fullscreen mode

Then, define a BookQueryResolver to handle getBooks and other queries, as shown above.

2. Essential Best Practices

2.1 Schema Design

Schema-first approach : The schema is defined first, often making it easier to maintain consistency across teams. Code-first: The schema is generated based on the code, useful for rapid prototyping.

Use custom scalar types to enforce strict typing. For example, you might add a LocalDate scalar for publication dates, ensuring proper validation.

2.2 Optimizing for Over-Fetching and Under-Fetching

With REST, the client often over-fetches data, receiving fields it doesn't need. With GraphQL, clients request only the fields required, reducing data payload and improving performance.

However, if a GraphQL query requires multiple database calls (the N+1 problem), it can lead to performance issues. DataLoader helps by batching database calls:

DataLoader<Long, Author> authorLoader = DataLoader.newMappedDataLoader(authorIds ->
    CompletableFuture.supplyAsync(() -> authorRepository.findAuthorsByIds(authorIds))
);
Enter fullscreen mode Exit fullscreen mode

2.3 Securing GraphQL Endpoints

Authentication : Secure with JWT, protecting routes via Spring Security. Example:

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String jwt = getJwtFromRequest(request);
        if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
            Authentication auth = tokenProvider.getAuthentication(jwt);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(request, response);
    }
}
Enter fullscreen mode Exit fullscreen mode

Authorization : Use @PreAuthorize on sensitive data resolvers to limit access based on roles.

2.4 Caching for Performance

Implement server-side caching with Caffeine or Redis to improve response times. Caffeine example:

@Cacheable("books")
public List<Book> getBooks() {
    return bookRepository.findAll();
}
Enter fullscreen mode Exit fullscreen mode

3. Advanced Concepts

3.1 Real-Time Data with Subscriptions

Subscriptions provide real-time data updates. For instance, you could notify clients when a book is added:

@SubscriptionMapping
public Flux<Book> bookAdded() {
    return Flux.create(sink -> bookAddedSink = sink);
}
Enter fullscreen mode Exit fullscreen mode

Set up WebSocket support, as GraphQL does not support HTTP-based subscriptions.

3.2 Building a Hybrid GraphQL and REST API

Some scenarios require both GraphQL and REST: Use REST for simple CRUD operations and GraphQL for querying complex, nested data.

4. Conclusion

A GraphQL API in Spring Boot offers flexibility and precision, but you must carefully design it. From setting up the schema to optimizing performance and securing the endpoints, this article has covered every necessary detail. Dive into these concepts and share your thoughts in the comments.

Read posts more at : Integrate Spring Boot with GraphQL for Highly Flexible APIs

Top comments (0)