Introduction
Every field in a GraphQL schema is backed by a resolver function that produces the field's value. Understanding how resolvers compose into a tree is the key mental model for building efficient GraphQL APIs with Hot Chocolate.
The Resolver Tree#
When Hot Chocolate receives a query, it builds a resolver tree that mirrors the shape of the request. Consider this query:
query {
me {
name
company {
id
name
}
}
}This produces the following resolver tree:
The execution engine traverses this tree starting from root resolvers. A child resolver can only execute after its parent has produced a value. Sibling resolvers at the same level run in parallel. Because of this parallel execution, resolvers (except top-level mutation field resolvers) must be free of side effects.
Execution completes when every resolver in the tree has produced a result.
Defining a Resolver#
Resolvers can be defined in a way that should feel very familiar to C# developers, as they either translate to methods or delegates.
Async Resolver#
Resolvers can be synchronous or asynchronous. Most data fetching operations, such as calling a service or database, are asynchronous.
The most important aspect of async resolvers is to honor the CancellationToken. This allows execution to be cancelled if the client abandons the request, preventing unnecessary work and resource usage.
Arguments#
In GraphQL, fields are conceptually similar to methods in C#. Just like methods, fields can have arguments, and you can access these argument values directly in your resolvers.
Injecting Services#
Hot Chocolate automatically recognizes types registered in the DI container and injects them into resolver parameters.
public class Query
{
public List<User> GetUsers(UserService userService)
=> userService.GetUsers();
}While you can take attributes to annotate services, you do not have to for non-keyed services.
public class Query
{
public List<User> GetUsers([Service] UserService userService)
=> userService.GetUsers();
}Learn more about dependency injection
Accessing parent values#
Each field resolver has access to the value that was resolved for its parent type.
For example, consider the following schema:
type Query {
me: User!;
}
type User {
id: ID!;
friends: [User!]!;
}The User schema type is represented by a User runtime class. The id field is a property on this class.
public class User
{
public string Id { get; set; }
}The friends resolver, by contrast, is independent: it is not declared on the User type and uses the user's Id to compute its result.
From the friends resolver's perspective, the User runtime object is its parent.
Access the parent value like this:
What's in This Section#
- Dependency Injection explains how services are injected into resolvers, scoping behavior, keyed services, and switching the service provider.
- Errors covers how exceptions become GraphQL errors, error filters for mapping domain exceptions, and throwing
GraphQLExceptionfor explicit errors. - Field Middleware shows how to build reusable middleware that runs before or after resolvers, including ordering, class-based middleware, and attribute-based middleware.