A modern, type-safe GraphQL library for Zig that makes building GraphQL APIs simple and enjoyable.
- π― Type-Safe: Leverage Zig's compile-time type system for GraphQL schemas
- π§ Fluent API: Intuitive builder pattern for queries, mutations, and schemas
- β‘ Zero Dependencies: Pure Zig implementation with no external dependencies
- π¦ Modular Design: Use only what you need - query builder, schema, parser, executor
- π¨ Clean Syntax: Idiomatic Zig code that feels natural
- π§ͺ Well Tested: Comprehensive test coverage
- π Production Ready: Complete examples and documentation
# See it in action!
git clone https://github.com/yousif-wali/grapzig
cd grapzig
# Run the complete GraphQL server example
zig build run-graphql-server
# This shows EXACTLY how Grapzig processes GraphQL queries!Add grapzig as a dependency in your build.zig.zon:
zig fetch --save "git+https://github.com/yousif-wali/grapzig#master"Then in your build.zig, add the grapzig module as a dependency to your program:
const grapzig = b.dependency("grapzig", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("grapzig", grapzig.module("grapzig"));Note: The library tracks Zig master (0.15.2+).
Grapzig is a GraphQL engine for building GraphQL servers in Zig.
Think of it as the GraphQL equivalent of:
graphql-jsfor Node.jsjuniperfor Rustgraphenefor Python
- β Parse GraphQL queries from HTTP requests
- β Validate queries against your schema
- β Define your GraphQL schema
- β Build queries/mutations programmatically
- π§ HTTP server (using
std.httpor your framework) - π§ Resolvers (functions that fetch data)
- π§ Database layer (PostgreSQL, MongoDB, etc.)
- π§ JSON response building
const std = @import("std");
const grapzig = @import("grapzig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Create a query
var query = grapzig.Query.init(allocator);
defer query.deinit();
// Build: { user(id: 1) { name email } }
var user_field = try query.field("user");
_ = try user_field.arg("id", grapzig.Value.fromInt(1));
var name = try user_field.select("name");
name.done();
var email = try user_field.select("email");
email.done();
user_field.done();
const query_string = try query.build();
defer allocator.free(query_string);
std.debug.print("{s}\n", .{query_string});
}Output:
query {
user(id: 1) {
name
email
}
}var mutation = grapzig.Mutation.init(allocator);
defer mutation.deinit();
var create = try mutation.field("createUser");
_ = try create.arg("name", grapzig.Value.fromString("Alice"));
_ = try create.arg("email", grapzig.Value.fromString("[email protected]"));
var id = try create.select("id");
id.done();
create.done();
const mutation_str = try mutation.build();
defer allocator.free(mutation_str);Client Request (HTTP POST)
β
Parse JSON body to get query string
β
grapzig.Parser.parse(query_string)
β
grapzig.Validator.validate(document, schema)
β
Execute query (call your resolvers)
β
Build JSON response
β
Send HTTP response to client
const std = @import("std");
const grapzig = @import("grapzig");
// 1. Define your data models
const User = struct {
id: u32,
name: []const u8,
email: []const u8,
};
const Post = struct {
id: u32,
title: []const u8,
author_id: u32,
};
// 2. Build your GraphQL schema (once at startup)
pub fn buildSchema(allocator: std.mem.Allocator) !grapzig.Schema {
const user_type = try allocator.create(grapzig.ObjectType);
user_type.* = grapzig.ObjectType.init(allocator, "User");
try user_type.addField("id", grapzig.FieldDefinition.init(
allocator, "id", .{ .scalar = .id }
));
try user_type.addField("name", grapzig.FieldDefinition.init(
allocator, "name", .{ .scalar = .string }
));
const query_type = try allocator.create(grapzig.ObjectType);
query_type.* = grapzig.ObjectType.init(allocator, "Query");
try query_type.addField("user", grapzig.FieldDefinition.init(
allocator, "user", .{ .object = user_type }
));
return grapzig.Schema.init(allocator, query_type);
}
// 3. Handle incoming GraphQL requests
pub fn handleGraphQLRequest(
allocator: std.mem.Allocator,
query_string: []const u8,
schema: *grapzig.Schema,
) ![]const u8 {
// Parse the query
var parser = grapzig.Parser.init(allocator, query_string);
var document = try parser.parse();
defer document.deinit();
// Validate the query
var validator = grapzig.Validator.init(allocator, schema);
defer validator.deinit();
if (!try validator.validate(&document)) {
return try buildErrorResponse(allocator, validator.getErrors());
}
// Execute the query (call your resolvers)
return try executeQuery(allocator, &document);
}Use ArenaAllocator for request-scoped allocations:
pub fn handleRequest(request: []const u8) ![]const u8 {
// Create arena for this request - everything auto-freed at end
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // Frees everything at once
const allocator = arena.allocator();
return try processGraphQLQuery(allocator, request);
}Note on Examples: Some examples may show memory leak warnings from
GeneralPurposeAllocator. This is expected and acceptable for demonstration code. In production, useArenaAllocatorfor automatic cleanup (seegraphql_server.zigandschema.zigexample).
Build GraphQL queries programmatically.
var query = grapzig.Query.init(allocator);
defer query.deinit();
var field = try query.field("fieldName");
_ = try field.arg("argName", grapzig.Value.fromInt(123));
var subField = try field.select("subField");
subField.done();
field.done();
const query_str = try query.build();
defer allocator.free(query_str);Build GraphQL mutations.
var mutation = grapzig.Mutation.init(allocator);
defer mutation.deinit();
var create = try mutation.field("createItem");
_ = try create.arg("name", grapzig.Value.fromString("Item"));
create.done();
const mutation_str = try mutation.build();
defer allocator.free(mutation_str);Define your GraphQL schema.
const user_type = try allocator.create(grapzig.ObjectType);
user_type.* = grapzig.ObjectType.init(allocator, "User");
try user_type.addField("id", grapzig.FieldDefinition.init(...));
const query_type = try allocator.create(grapzig.ObjectType);
query_type.* = grapzig.ObjectType.init(allocator, "Query");
var schema = grapzig.Schema.init(allocator, query_type);
defer schema.deinit();Parse GraphQL query strings.
var parser = grapzig.Parser.init(allocator, query_string);
var document = try parser.parse();
defer document.deinit();Validate queries against a schema.
var validator = grapzig.Validator.init(allocator, &schema);
defer validator.deinit();
const is_valid = try validator.validate(&document);
if (!is_valid) {
for (validator.getErrors()) |err| {
std.debug.print("Error: {s}\n", .{err.message});
}
}Represent GraphQL values.
const int_val = grapzig.Value.fromInt(42);
const str_val = grapzig.Value.fromString("hello");
const bool_val = grapzig.Value.fromBool(true);
const null_val = grapzig.Value.fromNull();
// Serialize to JSON
var buffer = std.ArrayList(u8){};
defer buffer.deinit(allocator);
try value.toJson(buffer.writer(allocator));The library includes comprehensive examples:
# Basic query and mutation building
zig build run-basic
# Schema definition
zig build run-schema
# Mutation examples
zig build run-mutations
# Server with parser and validator
zig build run-server
# π₯ COMPLETE GraphQL SERVER (Shows how Grapzig ACTUALLY works!)
zig build run-graphql-server
# Query/mutation building examples
zig build run-real-world-blogSee examples/graphql_server.zig - This is THE example that shows how Grapzig actually processes GraphQL queries on the server!
What it demonstrates:
- β Client sends GraphQL query string
- β Grapzig parses the query
- β Grapzig validates against schema
- β You execute by calling your resolvers (database operations)
- β Grapzig helped you understand WHAT to execute
- β Return JSON response to client
This example shows the complete flow of how mutations actually call your database functions!
-
Define Your Schema (once at startup)
var schema = try buildSchema(allocator); defer schema.deinit();
-
Handle HTTP Requests (per request)
pub fn handleRequest(query: []const u8) ![]const u8 { var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); return try processGraphQL(arena.allocator(), query, &schema); }
-
Parse & Validate
var parser = grapzig.Parser.init(allocator, query); var document = try parser.parse(); defer document.deinit(); var validator = grapzig.Validator.init(allocator, &schema); defer validator.deinit(); const is_valid = try validator.validate(&document);
-
Execute (call your resolvers)
const result = try executeQuery(allocator, &document, db);
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Build schema once at startup
var schema = try buildSchema(allocator);
defer schema.deinit();
// Start HTTP server
const address = try std.net.Address.parseIp("0.0.0.0", 8080);
var server = try std.net.Server.init(.{ .reuse_address = true });
defer server.deinit();
try server.listen(address);
std.debug.print("GraphQL server running on http://localhost:8080\n", .{});
while (true) {
const connection = try server.accept();
try handleConnection(allocator, connection, &schema);
}
}- Use
ArenaAllocatorfor request-scoped allocations - Build schema once at startup, not per request
- Always call
deinit()on resources
- Initialize schema once at server startup
- Reuse the same schema for all requests
- Keep schema in server state
var parser = grapzig.Parser.init(allocator, query);
var document = parser.parse() catch |err| {
return buildErrorResponse("Invalid query syntax");
};
defer document.deinit();- Reuse schema across requests
- Use arena allocators for requests
- Pool database connections
- Cache frequently used queries
Run the test suite:
zig build testBuild all examples:
zig build examples- Examples: See
examples/directory for working code - API Reference: See sections above
- Contributing: See CONTRIBUTING.md
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See CONTRIBUTING.md for detailed guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by the GraphQL specification
- Built with β€οΈ for the Zig community
- GitHub: @yousif-wali
- Issues: GitHub Issues
Made with β‘ by the Zig community
π₯π₯π₯π₯