CodeGym /Courses /Module 5. Spring /Lecture 286: Introduction to Data Fetchers and Their Role...

Lecture 286: Introduction to Data Fetchers and Their Role in GraphQL

Module 5. Spring
Level 15 , Lesson 5
Available

Imagine you're at a restaurant: the waiter hands you a menu and you pick dishes. The waiter writes down your order, goes to the kitchen where the chef prepares the food, and then brings it back to your table. In the GraphQL world, Data Fetchers are the waiters who "fetch data" for queries. They run the logic, pull data from databases, REST APIs, or other sources, and hand it back to the client.

A Data Fetcher is the mechanism responsible for implementing how the data described in your GraphQL schema will be obtained. It's the bridge between your schema and the actual data.


Why are Data Fetchers so important?

When a client makes a request to a GraphQL API, the server calls the appropriate Data Fetcher for each field in the query. Unlike REST or typical services, where you as a developer usually implement a single endpoint to return data, GraphQL requires a more granular approach: each field can have its own fetching logic.

Data Fetchers let you flexibly configure where and how data is retrieved: from a database, other APIs, cache, or even computed locally.


Differences between standard resolvers and Data Fetchers

In previous lectures we already met Query and Mutation Resolvers. Actually, those resolvers are basic Data Fetchers that handle top-level requests. But if you dig deeper, you'll see that Data Fetchers give you much finer control over the details of data handling.

So when should you stick with Query Resolvers, and when should you move to Data Fetchers? Here are some cases:

  • If your data depends on fields inside a type (for example, you need to compute something based on received data), you'll need Data Fetchers.
  • If data for different fields in the same type needs to come from different sources.
  • If you need custom logic for specific fields or you're integrating data from many sources.

Practical use of Data Fetchers

1. Implementing a basic Data Fetcher

Let's start with a classic simple example. Say we have a Book object in our system. In the GraphQL schema it might look like this:


type Book {
    id: ID!
    title: String!
    author: Author!
}

The Author type:


type Author {
    id: ID!
    name: String!
}

And a GraphQL query:


query {
    book(id: 1) {
        title
        author {
            name
        }
    }
}

In this case, title and author.name should be handled via Data Fetchers.

1.1 Defining our Data Fetcher for title


@Component
public class TitleDataFetcher implements DataFetcher<String> {

    private final BookService bookService;

    public TitleDataFetcher(BookService bookService) {
        this.bookService = bookService;
    }

    @Override
    public String get(DataFetchingEnvironment environment) throws Exception {
        // Get the book ID from the query arguments
        Integer bookId = environment.getArgument("id");
        Book book = bookService.getBookById(bookId);
        return book.getTitle();
    }
}

Here the get method receives a DataFetchingEnvironment, which provides all info about the current request: arguments, context, etc.

1.2 Registering the Data Fetcher in the GraphQL schema

Create schema.graphqls:


type Query {
    book(id: ID!): Book
}

type Book {
    id: ID!
    title: String!
    author: Author!
}

type Author {
    id: ID!
    name: String!
}

Wire the Data Fetcher into RuntimeWiring:


@Configuration
public class GraphQLConfiguration {

    @Bean
    public RuntimeWiringConfigurer runtimeWiringConfigurer(TitleDataFetcher titleDataFetcher) {
        return wiring -> wiring
                .type("Book", builder -> builder
                    .dataFetcher("title", titleDataFetcher));
    }
}

2. Integrating remote sources (for example, a REST API)

Imagine author is a separate microservice that returns author data. Now we'll create a Data Fetcher for Author.


@Component
public class AuthorDataFetcher implements DataFetcher<Author> {

    private final RestTemplate restTemplate;

    public AuthorDataFetcher(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Override
    public Author get(DataFetchingEnvironment environment) throws Exception {
        // Get the parent object (Book)
        Book book = environment.getSource();
        // Fetch the book's author from another service
        ResponseEntity<Author> response = restTemplate.getForEntity(
                "http://author-service/authors/" + book.getAuthorId(), Author.class);
        return response.getBody();
    }
}

3. Custom data handling

Sometimes you may need to include custom logic. For example, you want to return a rating field that's computed dynamically.


@Component
public class RatingDataFetcher implements DataFetcher<Double> {

    @Override
    public Double get(DataFetchingEnvironment environment) throws Exception {
        // Generate a random rating from 1 to 5
        return Math.random() * 5 + 1;
    }
}

Complex cases: when Data Fetchers really help

1. Aggregating data from multiple sources

Say your GraphQL query asks for the reviews field for a book, which lives in another database. Instead of changing the main BookResolver, you can create a separate Data Fetcher for reviews.


@Component
public class ReviewsDataFetcher implements DataFetcher<List<Review>> {

    private final ReviewsService reviewsService;

    public ReviewsDataFetcher(ReviewsService reviewsService) {
        this.reviewsService = reviewsService;
    }

    @Override
    public List<Review> get(DataFetchingEnvironment environment) throws Exception {
        // Get the parent object (Book)
        Book book = environment.getSource();
        return reviewsService.getReviewsForBook(book.getId());
    }
}

2. Multiple Data Fetchers on the same field

Sometimes you may want to pick different Data Fetchers depending on some conditions. For example, the price field could come from different places:

  • If it's a physical book in stock — from the local database.
  • If it's a digital copy — from an external API.

Common mistakes

When working with Data Fetchers, beginners often make these mistakes. For example, they forget to optimize requests. If you call 10 Data Fetchers for one query and each one hits the database, it can lead to serious performance problems. The solution is to use batch loaders (we'll talk about them later) to minimize the number of calls.

People also often forget about exception handling. If your Data Fetcher throws an exception, it can "blow up" the whole query. Use a DataFetcherExceptionHandler to manage errors.


Wrapping up

Data Fetchers are a key tool in GraphQL that connect your schema to data sources and let you handle each request with maximum flexibility. With them you can not only scale your app but also significantly improve performance and flexibility, especially in a microservices architecture.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION