Skip to main content

Resolvers

Resolvers is the way for GQLXY to deliver your data through simple functions. There are many types of Resolvers available.

Static values

The simplest resolvers are values set at schema construction time:

Schema schema({
.typeDefs = R"(
type Query {
user: User
}
type User {
id: ID!
name: String!
email: String!
active: Boolean!
}
)",
.resolvers = {
{"Query", Resolver{
{"user", Resolver{
{"id", "1"},
{"name", "Alice"},
{"email", "alice@example.com"},
{"active", true}
}}
}}
}
});

Function resolvers

For dynamic data, you can use function resolver types. All receive a const ResolverArgs&:

FunctionResolver (sync)

{"user", FunctionResolver{[&db](const ResolverArgs& args) -> ValueResolver {
auto id = args.Args()["id"].get<std::string>();
auto user = db.findUser(id);
if (!user) return std::nullopt;
return Resolver{
{"id", user->id},
{"name", user->name},
{"email", user->email}
};
}}}

AsyncFunctionResolver (std::future)

{"user", AsyncFunctionResolver{[](const ResolverArgs& args) -> std::future<ValueResolver> {
return std::async(std::launch::async, [=]() -> ValueResolver {
return Resolver{
{"id", "1"},
{"name", "Alice"}
};
});
}}}

CoroutineResolver (C++20 coroutine)

{"user", CoroutineResolver{[](const ResolverArgs& args) -> Task<ValueResolver> {
auto user = co_await fetchUserAsync(args.Args()["id"].get<std::string>());
co_return Resolver{
{"id", user.id},
{"name", user.name}
};
}}}

CallbackResolver

{"user", CallbackResolver{[](const ResolverArgs& args, const std::function<void(const ValueResolver&)>& cb) {
fetchUserWithCallback(args.Args()["id"].get<std::string>(), [cb](const User& u) {
cb(Resolver{
{"id", u.id},
{"name", u.name}
});
});
}}}

Lists

{"users", FunctionResolver{[](const ResolverArgs&) -> ValueResolver {
auto users = allUsers();
return users | std::views::transform([](const auto& u) -> ValueResolver {
return Resolver{
{"id", u.id},
{"name", u.name}
};
});
}}}

Mutations

To define mutation resolvers, you can use the Mutation type. If you want to execute multiple mutations, they will be executed serially:

{"Mutation", Resolver{
{"addBook", FunctionResolver{[](const ResolverArgs& args) -> ValueResolver {
auto title = args.Args()["title"].get<std::string>();
auto author = args.Args()["author"].get<std::string>();
return Resolver{
{"id", "new-id"},
{"title", title},
{"author", author}
};
}}}
}}

Context

GQLXY handles request-scoped state (auth tokens, DB connections, etc.) through SchemaResolveArgs::context:

struct RequestContext {
std::string userId;
};

auto result = schema.Resolve({
.query = "{ me { name } }",
.context = RequestContext{"42"}
}).get();

You can access it inside any resolver like this:

{"me", FunctionResolver{[](const ResolverArgs& args) -> ValueResolver {
auto& ctx = args.Context<RequestContext>();
return Resolver{
{"id", ctx.userId}
};
}}}

Aliases, fragments, and interfaces

Aliases, named/inline fragments, and interface/union TypeResolver are all handled automatically. See Unions & Interfaces for type resolution.