Resolvers
The convention that wires a WireBox mapping to a schema type, and the four arguments every resolver method gets.
The convention
For a schema type TypeName with field fieldName, the module looks for a WireBox mapping at {resolverBasePackage}.{TypeName}Resolver and, if it exists and implements a fieldName() method, calls it. Otherwise it falls back to graphql-java's PropertyDataFetcher — reading a same-named key off the parent object. This means you only write a resolver for fields that need custom logic; a struct with matching keys just works.
Resolver method signature
Resolver methods receive four named arguments:
cfscriptany function fieldName( any source, struct args, any context, any env ){
// source — the parent object (struct/whatever the parent resolver returned).
// NULL for root Query/Mutation fields — don't mark it `required`.
// args — the field's GraphQL arguments, as an ordinary CFML struct/array
// (already converted from graphql-java's raw Map — case-insensitive
// dot access works as normal).
// context — the ColdBox `event` (RequestContext) for the current request, so you can
// reach rc/prc, headers, session, etc. via the normal ColdBox API.
// env — the raw graphql.schema.DataFetchingEnvironment, for advanced use.
}
Example
schema.graphqls:
graphqltype Query {
widget(id: ID!): Widget
}
type Widget {
id: ID!
name: String
slug: String
}
models/resolvers/QueryResolver.cfc:
cfscriptcomponent {
property name="widgetService" inject="WidgetService";
any function widget( any source, required struct args, any context, any env ){
return widgetService.get( args.id ); // returns a struct with id/name/slug
}
}
No WidgetResolver.cfc is needed at all — id, name, and slug all resolve via PropertyDataFetcher off the struct widget() returned. If you later need slug computed rather than stored, add models/resolvers/WidgetResolver.cfc with just a slug() method; id and name keep resolving automatically.
Mutations
Mutation gets no special treatment — it's just another object type name to the wiring loop in GraphQLService.wireResolvers(), so it follows the exact same {resolverBasePackage}.{TypeName}Resolver convention as Query, with the exact same four named arguments.
Extending the schema above with a mutation:
graphqltype Mutation {
createWidget(name: String!): Widget!
deleteWidget(id: ID!): Boolean!
}
models/resolvers/MutationResolver.cfc:
cfscriptcomponent {
property name="widgetService" inject="WidgetService";
any function createWidget( any source, required struct args, any context, any env ){
return widgetService.create( args.name ); // returns a struct with id/name/slug
}
boolean function deleteWidget( any source, required struct args, any context, any env ){
return widgetService.delete( args.id );
}
}
The result of createWidget flows back through the schema exactly like a query result does — since it returns a struct shaped like Widget, the id/name/slug fields on the response resolve via the same PropertyDataFetcher fallback, no extra wiring needed.
One thing the module does not do anything special for: per the GraphQL spec, top-level mutation fields in a single request execute serially, in the order they appear in the query, rather than in parallel like top-level query fields. That's handled entirely by graphql-java's own execution strategy once it sees a Mutation root type — nothing to configure here.
TypeResolver wiring — every object type gets its own convention-based resolver, but resolving which concrete type implements an interface/union at runtime is not wired up.