Thunk-based Resolvers: Building a Flexible GraphQL API Gateway - WunderGraph

State of GraphQL Federation 2026

At the core of WunderGraph is a very powerful and flexible GraphQL API Gateway. Today, we'll dive deep into the pattern that powers it: Thunk-based GraphQL Resolvers.

Learning more about the Thunk-based Resolver pattern will help you understand how you can build your own custom GraphQL Gateway. If you're familiar with Go, you also don't have to start from scratch as you can build upon our Open Source Framework. If you'd like to implement this idea in another language, you can still learn a lot about the patterns as this post will not focus on the Go implementation.

To start off, let's define the goals of a GraphQL API Gateway:

Building a GraphQL API Gateway that is able to support these requirements is a real challenge. To better understand the problem and how Thunk-based Resolvers help solve it, let's take a look at the differences between a "regular" resolver and a "thunk-based" resolver first.

A Regular GraphQL Resolver

const userResolver = async (id) => {
  const user = await db.userByID(id)
}

This is a simple user resolver. It takes a user ID as an argument and returns a user object. What's important to note is that this function returns data if you execute it.

A Thunk-based GraphQL Resolver

A thunk-based resolver on the other hand doesn't return data immediately. Instead, it returns a function that you can execute later.

Here's an example:

const userResolver = async () => {
  return async (root, args, context, info) => {
    const user = await db.userByID(args.id)
    return user
  }
}

This is a thunk-based resolver. It doesn't return data immediately. Instead, it returns a function than can be used later to load the user.

A Regular GraphQL Server

In a regular GraphQL server, the execution flow of a GraphQL Query looks like this:

  1. Parsing of the GraphQL Query
  2. Normalization
  3. Validation
  4. Execution

During the Execution phase, the GraphQL Server will execute the resolvers and assemble the result.

Here's a simple GraphQL Schema:

type Query {
    user(id: ID!): User
}
type User {
    id: ID!
    name: String!
    posts: [Post]
}
type Post {
    id: ID!
    title: String!
    body: String!
}

...with these two GraphQL Resolvers...

const userResolver = async (userID: string) => {
  const user = await db.userByID(userID)
  return user
}
const postResolver = async (userID: string) => {
  const post = await db.postsByUserID(userID)
  return post
}

...and the following GraphQL Query:

query {
  user(id: "1") {
    id
    name
    posts {
      id
      title
      body
    }
  }
}

The execution of this query will look like this:

  1. walk into the field user and execute the userResolver with the argument "1"
  2. resolve the fields id and name of the user object
  3. walk into the field posts and execute the postResolver with the argument "1"
  4. resolve the fields id, title and body of the post object

Once executed, the response might look like this:

{
  "data": {
    "user": {
      "id": "1",
      "name": "John Doe",
      "posts": [
        {
          "id": "1",
          "title": "Hello World",
          "body": "This is a post"
        }
      ]
    }
  }
}

This should be enough context to understand the problem of building an API Gateway. You should be able to point your GraphQL API Gateway at your GraphQL Server, and it should be able to execute the GraphQL Query. This is where thunk-based resolvers come into play.

A Thunk-based GraphQL Server

Thunk-based GraphQL Servers are different from regular GraphQL Servers in that they split the execution of a GraphQL Query into two phases, planning and execution.

The Execution Phase of a Thunk-based GraphQL Server

The execution phase depends on the execution plan. This plan is created based on the query, which translates into a response.

The Planning Phase of a Thunk-based GraphQL Server

The configuration contains all information about the GraphQL Schema and how to resolve Queries. For example, if a query starts with the root field "user", it will be resolved by the DataSource responsible for this field.

The Challenges of building a Thunk-based GraphQL Framework

Challenges include manually configuring thunk-based resolvers, distinguishing between upstream and downstream Schema, and managing naming collisions between schemas.

Benefits of the Thunk-based approach to GraphQL Resolvers

The thunk-based approach allows for more efficient batching and can offload complex computational logic, improving performance.

Thunk-based Resolvers are no replacement for the "classic" Resolver approach

While powerful, thunk-based resolvers are specially suited for API Gateways rather than general GraphQL servers.

How we've made configuring thunk-based resolvers easy

Using a TypeScript SDK can simplify the configuration process, allowing for automated configuration and updates without manual intervention.

Did we meet the requirements?

Summary

This article covered the creation of a GraphQL API Gateway using Thunk-based Resolvers, highlighting the differences from traditional resolvers and their specific advantages for building Gateways.