Guides

Middleware

Hot Chocolate has three kinds of middleware. The query middleware which allows to extend or rewrite the processing of a query request, the field middleware which allows to extend or rewrite the processing of field resolvers and the directive middleware which allows basically to add a field middleware to fields that are annotated with a specific directive.

Field Middleware

The most common way to extend the execution is to extend the pipeline that resolves data from a field.

The field resolver itself is embedded in a middleware that will call the field's resolver if no other middleware component has produced a result for the field.

A field middleware can be used to convert the result of a field to fetch the result from a different source or even validate the arguments of a field. There are multiple use cases for which a field middleware is useful.

A field middleware can be bound to a specific field or it can be included into the field resolver pipeline of all fields.

So, let us first have a look at the simplest case where we add a field middleware to every field of the middleware.

Our middleware shall resolve the field data if the source-object (parent-object) that is passed down to the field resolver pipeline is a dictionary.

In your middleware you can always decide if your middleware completes the pipeline or if it shall call the next pipeline component.

In the above example we are completing (short-circuiting) the middleware pipeline if the source-object is a dictionary and we have resolved the field result; otherwise, we are calling the next middleware component in the pipeline.

Our middleware could also pass to the next pipeline if we want to allow other middleware components to be able to further process the result or even replace result with a new result.

Another pattern is to reverse the execution of our middleware and first let the middleware components that come after our middleware process. This will let the other middleware compose the field result.

Our field middleware can now convert the result that some other middleware component has produced.

Our paging implementation for IQueryable is a field middleware and is provided through an extension method on IObjectFieldDescriptor.

The extension method adds the middleware as well as the arguments that the middleware expects.

descriptor.Field(t => Bar).UsePaging();

The extension method hides the complexity of combining a middleware with arguments and so on and also reduces repetitive code.

Executor Bound Middleware

Field middleware components can also be declared on the QueryExecutionBuilder, this way the execution engine can be extended without having to declare field middleware components on a schema and query middleware components on the executor. The UseField method let you consistently extend the execution engine through one interface.

So, when should we put a field middleware on the schema level and when on the executor level.

We should put anything on the schema level that is needed to make the schema work properly. Everything, that changes the way the query engine works or infrastructure components should go on the executor level since those are exchangeable. This is especially true when you combine a query middleware with a field middleware.

As a side note, the IMiddlewareContext implements also IResolverContext so in a middleware you have access to all the context information that the resolver context has.
You can even access all the results that the previous resolver in your path have produced by accessing the Source property which is exposed as a immutable stack of results.

Directive Middleware

Directives can be used to annotate nearly everything in your schema or query. The annotation can than be used in a field middleware to change the way something is executed and so on.

In order to make directives even more powerful we added the ability to define a directive middleware which is executed whenever a directive is annotated to an object definition, field definition or field selection.

So, first lets have a look at how to define a directive middleware.

Let's say we want to have a directive that always converts the result of annotated fields to an upper string.

Query Middleware

The query execution process itself is just made up of many query middleware components.

For us it makes changes to the execution pipeline very simple. Moreover, we can write tests for each middleware component.

Furthermore, with the QueryExecutionBuilder you are able to rewrite our execution pipeline.

We are using this very thing to implement our schema stitching API. Basically we swapped out the parser middleware for one that parses and rewrites queries in order to delegate parts of the query to remote schemas.

So, when you want to rewrite the execution process itself then a query middleware is what you want to do implement.