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:
- It should be easy to configure and extend
- It should be able to mediate between different services and protocols
- It should be possible to re-deploy without a code-generation / compilation step
- It should support schema stitching
- It should support Apollo Federation
- It should support Subscriptions
- It should support REST APIs
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:
- Parsing of the GraphQL Query
- Normalization
- Validation
- 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:
- walk into the field user and execute the userResolver with the argument "1"
- resolve the fields id and name of the user object
- walk into the field posts and execute the postResolver with the argument "1"
- 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?
- Easy to configure and extend: Yes, using the SDK.
- Mediate between different services and protocols: Yes, supports many protocols.
- Re-deploy without a code-generation / compilation step: Yes, execution plans can be cached.
- Support for schema stitching and Apollo Federation: Yes, these features are included.
- Support Subscriptions and REST APIs: Yes, both are supported in the framework.
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.