Skip to content

GraphQL

Lee Yunjin edited this page Oct 7, 2026 · 1 revision

GraphQL

Headers: <cwist/net/graphql/graphql.h> (queries and mutations), <cwist/net/graphql/graphql_ws.h> (subscriptions). <cwist/graphql.h> and <cwist/graphql_ws.h> include them.

CWIST's GraphQL layer maps top-level Query, Mutation and Subscription fields to C resolvers that return cJSON values. It is bounded by design: no schema language, no introspection and no type system beyond what the resolvers return.

What the executor handles: root fields with arguments (literals or $variables), aliases, several root fields in one operation, named operations and mutation operations. A resolver's return value is placed in data as is.

What it does not handle: selection sets on a field's result. A query such as { user(id: "7") { id name } } currently returns the resolver's object together with "Cannot query field" errors for the sub-fields (and the sub-field names repeated with null values). Query the root field without a selection set ({ user(id: "7") }) and have the resolver return the shape you want.

Queries and mutations

#include <cwist/app.h>
#include <cwist/net/graphql/graphql.h>

static cJSON *user(const cJSON *args, const cJSON *variables, void *ctx) {
    (void)variables; (void)ctx;
    const cJSON *id = cJSON_GetObjectItemCaseSensitive(args, "id");
    cJSON *o = cJSON_CreateObject();
    cJSON_AddStringToObject(o, "id", cJSON_IsString(id) ? id->valuestring : "?");
    cJSON_AddStringToObject(o, "name", "Ada");
    return o;                          /* CWIST takes ownership */
}

static cwist_graphql_schema_t *schema;

static void graphql(cwist_http_request *req, cwist_http_response *res) {
    cwist_graphql_serve(schema, req, res);
}

int main(void) {
    schema = cwist_graphql_schema_create();
    cwist_graphql_add_query(schema, "user", user, NULL);
    cwist_app *app = cwist_app_create();
    cwist_app_post(app, "/graphql", graphql);
    cwist_app_listen(app, 8080);
    cwist_app_destroy(app);
    cwist_graphql_schema_destroy(schema);
}
curl -s localhost:8080/graphql -H 'Content-Type: application/json' \
     -d '{"query":"{ user(id: \"7\") }"}'
# {"data":{"user":{"id":"7","name":"Ada"}}}
Function Description
cwist_graphql_schema_t *cwist_graphql_schema_create(void) / void cwist_graphql_schema_destroy(cwist_graphql_schema_t *s) Schema lifecycle.
bool cwist_graphql_add_query(cwist_graphql_schema_t *s, const char *field, cwist_graphql_resolver_fn fn, void *ctx) Register or replace a Query field.
bool cwist_graphql_add_mutation(cwist_graphql_schema_t *s, const char *field, cwist_graphql_resolver_fn fn, void *ctx) Register or replace a Mutation field.
cwist_error_t cwist_graphql_execute(cwist_graphql_schema_t *s, const char *request_json, cwist_sstring **out_json) Execute a JSON request (query, optional operationName and variables) and produce the JSON response.
void cwist_graphql_serve(cwist_graphql_schema_t *s, cwist_http_request *req, cwist_http_response *res) Handler adapter for POST routes. The body must be the JSON request object ({"query": ...}); other methods and bodies get 400.
typedef cJSON *(*cwist_graphql_resolver_fn)(const cJSON *args, const cJSON *variables, void *ctx);

args is the field's parsed arguments (or NULL), variables the request variables. Return a new cJSON value; CWIST frees it. Resolvers run on the request thread and can run concurrently.

Limits: CWIST_GRAPHQL_MAX_QUERY_SIZE (64 KiB), CWIST_GRAPHQL_MAX_FIELDS (128) and CWIST_GRAPHQL_MAX_DEPTH (16).

Subscriptions

Supported since v3.8 and opt-in. Subscriptions use the graphql-transport-ws protocol over the reactor WebSocket transport, so they need the reactor server (C1M mode).

Protocol behavior:

  • connection_init is answered with connection_ack; a subscribe before init closes the socket with code 4429.
  • subscribe {id, payload:{query, variables, operationName}} produces one next {id, payload:{data:{...}}} per published event, error {id, payload:[...]} when the operation cannot start, and complete {id} when it ends.
  • ping/pong keepalive after init. Malformed messages close with 4400/4401; a duplicate operation id closes with 4409.
  • A client complete or a socket close tears the subscription down.
#include <cwist/net/graphql/graphql_ws.h>

static cwist_graphql_event_source_t *counter_subscribe(const cJSON *args, const cJSON *vars, void *ctx) {
    (void)args; (void)vars; (void)ctx;
    cwist_graphql_event_source_t *src = cwist_graphql_event_source_create();
    cwist_graphql_event_source_add_topic(src, "counter");
    return src;                        /* NULL rejects the operation */
}

cwist_graphql_ws_t *gws = cwist_graphql_ws_create(schema);
cwist_graphql_ws_add_subscription(gws, "counter", counter_subscribe, NULL);
/* hand an upgraded socket to the endpoint: */
cwist_graphql_ws_attach(gws, fd, reactor, initial_bytes, initial_len);

/* anywhere, any thread: */
cJSON *payload = cJSON_CreateNumber(42);
cwist_graphql_publish("counter", payload);   /* -> next {data:{counter: 42}} */
cJSON_Delete(payload);
Function Description
cwist_graphql_ws_t *cwist_graphql_ws_create(cwist_graphql_schema_t *schema) Endpoint bound to a schema (not owned; must outlive the endpoint).
void cwist_graphql_ws_destroy(cwist_graphql_ws_t *gws) Destroy after every attached connection is gone (after the reactors stopped).
bool cwist_graphql_ws_add_subscription(gws, field, subscribe_fn, ctx) Register or replace a subscription field. The resolver runs on the reactor thread for each subscribe.
bool cwist_graphql_ws_attach(gws, fd, reactor, initial, initial_len) Hand an upgraded socket to the protocol handler; the HTTP layer must not touch the fd afterwards. false closes the fd.
cwist_graphql_event_source_create, _add_topic, _destroy Topic set a subscription listens to; destroyed automatically when the operation ends.
size_t cwist_graphql_publish(const char *topic, const cJSON *payload) Publish to the in-process broker (payload borrowed; NULL sends null). Thread-safe; delivery happens on each connection's reactor thread. Returns the number of next messages queued.
bool cwist_graphql_parse_subscription(query, variables, field_out, field_cap, &args_out) Extract the root field and arguments of a subscription operation (used by the protocol layer).

The broker is in-process: events published in one worker process reach only subscribers connected to that process. Tests: tests/test_graphql.c, tests/test_graphql_subscriptions.c.

Clone this wiki locally