GraphQL Resolvers API
API reference for GraphQL resolver implementation in api.zig.
Executor
Executor.init
zig
pub fn init(allocator: std.mem.Allocator, schema: *Schema) ExecutorCreate a new GraphQL executor.
Example:
zig
var executor = api.graphql.Executor.init(allocator, &schema);registerResolver
zig
pub fn registerResolver(
self: *Executor,
type_name: []const u8,
field_name: []const u8,
resolver: ResolverFn,
) !voidRegister a field resolver.
Example:
zig
try executor.registerResolver("Query", "users", usersResolver);
try executor.registerResolver("Query", "user", userResolver);
try executor.registerResolver("User", "posts", userPostsResolver);execute
zig
pub fn execute(
self: *Executor,
query: []const u8,
variables: ?std.StringHashMap(Value),
context: anytype,
) !ExecutionResultExecute a GraphQL query.
Example:
zig
const result = try executor.execute(
"query { users { id name } }",
null,
&resolver_context,
);Resolver Function Types
ResolverFn
zig
pub const ResolverFn = *const fn (
ctx: *anyopaque,
args: ArgumentMap,
) anyerror!Value;Standard resolver function type.
FieldResolverFn
zig
pub const FieldResolverFn = *const fn (
ctx: *anyopaque,
parent: Value,
args: ArgumentMap,
) anyerror!Value;Field resolver with parent value.
SubscriptionResolverFn
zig
pub const SubscriptionResolverFn = *const fn (
ctx: *anyopaque,
args: ArgumentMap,
) anyerror!AsyncIterator;Subscription resolver returning async iterator.
Value Type
Value Union
zig
pub const Value = union(enum) {
null: void,
int: i64,
float: f64,
string: []const u8,
boolean: bool,
list: []const Value,
object: std.StringHashMap(Value),
enum_value: []const u8,
variable: []const u8,
};Value Methods
zig
// Get as specific type
pub fn getString(self: Value) ?[]const u8
pub fn getInt(self: Value) ?i64
pub fn getFloat(self: Value) ?f64
pub fn getBool(self: Value) ?bool
pub fn getList(self: Value) ?[]const Value
pub fn getObject(self: Value) ?std.StringHashMap(Value)
// Create error value
pub fn error(message: []const u8, code: []const u8) ValueArgumentMap
Methods
zig
pub fn get(self: ArgumentMap, key: []const u8) ?Value
pub fn getString(self: ArgumentMap, key: []const u8) ?[]const u8
pub fn getInt(self: ArgumentMap, key: []const u8) ?i64
pub fn getFloat(self: ArgumentMap, key: []const u8) ?f64
pub fn getBool(self: ArgumentMap, key: []const u8) ?bool
pub fn getObject(self: ArgumentMap, key: []const u8) ?std.StringHashMap(Value)ExecutionResult
zig
pub const ExecutionResult = struct {
data: ?Value,
errors: []const GraphQLError,
pub fn toJson(self: ExecutionResult, allocator: std.mem.Allocator) ![]const u8
};GraphQLError
zig
pub const GraphQLError = struct {
message: []const u8,
locations: []const Location = &.{},
path: []const PathSegment = &.{},
extensions: ?std.StringHashMap(Value) = null,
};
pub const Location = struct {
line: u32,
column: u32,
};
pub const PathSegment = union(enum) {
field: []const u8,
index: u32,
};DataLoader
Batch loading to prevent N+1 queries.
DataLoader.init
zig
pub fn init(
allocator: std.mem.Allocator,
batch_fn: BatchFn,
) DataLoaderload
zig
pub fn load(self: *DataLoader, key: K) !VLoad a single value (batched automatically).
loadMany
zig
pub fn loadMany(self: *DataLoader, keys: []const K) ![]VLoad multiple values.
Example:
zig
const UserLoader = api.graphql.DataLoader([]const u8, User);
fn batchLoadUsers(keys: []const []const u8) ![]User {
return db.getUsersByIds(keys);
}
var loader = UserLoader.init(allocator, batchLoadUsers);
const user = try loader.load("user-123");Resolver Context
Create a custom context struct:
zig
const ResolverContext = struct {
allocator: std.mem.Allocator,
db: *Database,
user: ?*AuthenticatedUser,
loaders: struct {
users: *UserLoader,
posts: *PostLoader,
},
};Example Resolvers
Query Resolver
zig
fn usersResolver(ctx_ptr: *anyopaque, args: ArgumentMap) !Value {
const ctx = @ptrCast(*ResolverContext, ctx_ptr);
_ = args;
const users = try ctx.db.getAllUsers();
var list = std.ArrayList(Value).init(ctx.allocator);
for (users) |user| {
var obj = std.StringHashMap(Value).init(ctx.allocator);
try obj.put("id", .{ .string = user.id });
try obj.put("name", .{ .string = user.name });
try list.append(.{ .object = obj });
}
return .{ .list = list.items };
}Field Resolver
zig
fn userPostsResolver(ctx_ptr: *anyopaque, parent: Value, args: ArgumentMap) !Value {
const ctx = @ptrCast(*ResolverContext, ctx_ptr);
const user_id = parent.getObject().?.get("id").?.string;
const limit = args.getInt("limit") orelse 10;
const posts = try ctx.loaders.posts.load(user_id);
// ... convert to Value
}Mutation Resolver
zig
fn createUserResolver(ctx_ptr: *anyopaque, args: ArgumentMap) !Value {
const ctx = @ptrCast(*ResolverContext, ctx_ptr);
const input = args.getObject("input") orelse return Value.error("Missing input", "BAD_REQUEST");
const name = input.getString("name") orelse return Value.error("Missing name", "BAD_REQUEST");
const email = input.getString("email") orelse return Value.error("Missing email", "BAD_REQUEST");
const user = try ctx.db.createUser(.{ .name = name, .email = email });
// ... convert to Value
}