Skip to main content

Resolvers

GraphQL resolvers execute the logic for each field and return the expected value typed according to the schema. In Dart this are functions that receive the parent's object value and the field's Ctx, and return the execution result. Simple fields may only return a property of the parent object value. However, there may also be complex resolvers, such as mutations, that validate the input data and create rows in a database, or queries that retrieve multiple rows according to complex authorization logic.

Queries and Mutations

Each field (GraphQLObjectField) in an object type (GraphQLObjectType) contains a resolve parameter, this will be used to resolve all fields. The first argument to resolve with be the parent object, if this field is in the root Query or Mutation Object, the value will be the the root value passed as an argument to GraphQL.parseAndExecute and a SubscriptionEvent if this is a subscription field (more in the subscription section). The second argument will be the field's Ctx, with it you can access defined Refs with Ref.get(ctx) and view more information about the resolved field or GraphQL request. When using package:leto_shelf, you can access the HTTP request and modify the HTTP response, more information in the package's README.

type Query {
someField: String
}

type CustomMutation {
updateSomething(arg1: Float): Date
}

"""An ISO-8601 Date."""
scalar Date

type schema {
query: Query
mutation: CustomMutation
}

In Dart:

final query = objectType(
'Query',
fields: [
graphQLString.field(
'someField',
resolve: (Object? rootObject, Ctx ctx) => 'someFieldOutput',
),
],
);

final customMutation = objectType(
'CustomMutation',
fields: [
graphQLDate.field(
'updateSomething',
inputs: [
graphQLFloat.inputField('arg1')
],
resolve: (Object? rootObject, Ctx ctx) {
final arg1 = ctx.args['arg1'] as double?;
return DateTime.now();
},
),
],
);

final schema = GraphQLSchema(
queryType: query,
mutation: customMutation,
);

When using package:leto_shelf, POST requests can be used for Queries or Mutations. However, GET requests can only be used for Queries, if a Mutation operation is sent using a GET request, the server will return a 405 status code (MethodNotAllowed) following the GraphQL over HTTP specification.

Subscriptions

Each field (GraphQLObjectField) in an object type (GraphQLObjectType) contains a subscribe parameter that receives the root value and a Ctx, and returns a Stream of values of the field's type Stream<T> Function(Ctx<P> ctx, P parent). The Stream of values will be returned in the data field of the GraphQLResult returned on execution.

If using a WebSocket server, the client should support either graphql-transport-ws or graphql-ws sub-protocols.


final apiSchema = GraphQLSchema(
queryType: objectType('Query'),
subscriptionType: objectType(
'Subscription',
fields: [
graphQLInt.nonNull().fields(
'secondsSinceSubscription',
subscribe: (Ctx ctx, Object rootValue) {
return Stream.periodic(const Duration(seconds: 1), (secs) {
return secs;
});
}
),
]
),
);

Future<void> main() async {
final GraphQLResult result = await GraphQL(apiSchema).parseAndExecute(
'subscription { secondsSinceSubscription }',
);

assert(result.isSubscription);
final Stream<GraphQLResult> stream = result.subscriptionStream!;
stream.listen((event) {
final data = event.data as Map<String, Object?>;
assert(data['secondsSinceSubscription'] is int);

print(data['secondsSinceSubscription']);
});
}

The resolve callback in a subscription field will always receive a SubscriptionEvent as it's parent. From that you can access the event value with SubscriptionEvent.value which will be the emitted by the Stream returned in the subscribe callback. The error handling in each callback is different, if an error is thrown in the subscribe callback, the Stream will end with an error. But if you throw an error in the resolve callback it will continue sending events, just the event resolved with a thrown Object will have GraphQLErrors as a result of processing the thrown Object (More information in Error Handling).

For usage in a web server you can use any of the web server integrations which support WebSocket subscriptions (For example, leto_shelf).

Examples

For a complete subscriptions example with events from a database please see the chat_example, in particular the events directory.

Request Contexts

All Ctxs implement ScopedHolder, so that then can be used to retrieve values from the scoped map, more in ScopedMap.

Ctx

Source Code

A unique context for each field resolver

  • args: the arguments passed as inputs to this field
  • object: the parent Object's value, same as the first parameter of resolve.
  • objectCtx: the parent Object's execution context (ObjectExecutionCtx)
  • field: The GraphQLObjectField being resolved
  • path: The path to this field
  • executionCtx: The request's execution context (ExecutionCtx)
  • lookahead: A function for retrieving nested selected fields. More in the LookAhead section

ObjectExecutionCtx

This is the context associated with an object execution, can be retrieved through Ctx.objectCtx. There will be as many instances as there are objects to execute in the request. Contains the value of the object, the field selections and the path in the GraphQL request to this object.

ExecutionCtx

This is the context associated with the execution phase of the request, created after the validation phase. Contains validated and coerced (parsed) input values and the specific validated operation within the request's document to execute. It has an errors list with the encountered errors during execution. Can be retrieved with ObjectExecutionCtx.executionCtx.

RequestCtx

This is the base context associated with the request, contains the raw information about the GraphQL document, the raw (not validated nor parsed) input values, input extensions, the schema, root value and the scoped map for this request. Can be retrieved with ExecutionCtx.requestCtx.