Creating Custom Middlewares
Anatomy of a Resolver Map Middleware
Resovler Map Middlewares represent the lazy application of changes to a Resolver Map. A Resolver Map Middleware receives a Resolver Map, along with some contextual options, and returns a Resolver Map. The Resolver Map returned does not need to be the same one that was passed in but it will represent the Resolver Map going forward.
async (resolverMap, packOptions) => {
// make any modifications and return a resolver map
return resolverMap;
}
with packOptions
representing:
{
state: Object
dependencies: Object
}
Note: Only these properties should be relied on. Do not add additional properties, there is no guarantee they will be preserved.
state
— Useful for any storing any references that should be persisted for accessing from the outside, via thestate
property on aGraphQLHandler
instance.dependencies
— Contains any external dependencies initially passed in when creating theGraphQLHandler
.
The packOptions
object is also available within Resolver Wrappers.
Adding options to a Middleware
The easiest way of adding options to a Resolver Map is to use a function factory that provides any additional options by its arguments which are in scope for the inner Resolver Map Middleware function.
const middlewareFunction = (options) => {
// return a resolver map middleware with options in scope
return (resolverMap, packOptions) => {
// ... do something with the `options` reference
return resolverMap;
}
}
Then it can be used where needed, for example:
const handler = new GraphQLHandler({
resolverMap,
middlewares: [middlewareFunction(options)],
});
highlight
option
For many Middlewares it is useful to provide a highlight
option when a Middleware can operate on user-defined portions of the GraphQL Schema. The highlight
option uses the Highlight system and conforms to the CoercibleHighlight
type.
By using CoercibleHighlight
it provides a flexible option by accepting:
- References, an array of References
- Highlight callback function
(h) => { return h.include(['Query', 'user']) }
, a callback where the highlight instance is setup and expects a returnedHighlight
instance. Highlight
instance, provided directly by the consumers of the middleware
These three options can be converted into a Highlight
instance with the coerceHighlight
utility.
If the default behavior is to "highlight the entire schema" for a the highlight
option the highlightAllCallback
can be used as the default value which will highlight everything in the schema.
import { coerceHighlight } from 'graphql-mocks/highlight/utils';
const middleware = ({ highlight }) => {
return (resolverMap, packOptions) => {
const graphqlSchema = packOptions.dependencies?.graphqlSchema;
// ensures that a Highlight instance is provided based from
// either references, a highlight callback, or a highlight instance
const coercedHighlight = coerceHighlight(highlight);
}
};
Handling External Dependencies
A dependency in this case is something external to the Resolver Map Middleware that can/must be provided for the Resolver Map Middleware. An example might be a reference to a global object, or an instance of
Shared Dependencies
If a dependency is considered shared amongst multiple Resolver Map Middlewares or Resolver Wrappers use the dependencies
the external dependencies, packOptions.dependencies
, provided on the second argument of a Resolver Map Middleware.
const middleware = return (resolverMap, packOptions) => {
// pull `fooDependency` reference off packOptions.dependencies
const foo = packOptions.dependencies?.fooDependency
if (!foo) {
throw new Error('`foo` is a required dependency');
}
}
Isolated Dependencies
When a dependency is used only for a single instance of a middleware it can be provided as an option in a factory function.
const middlewareFunction = ({ someDependency }) => {
return (resolverMap, packOptions) => {
// ... do something with the `someDependency` reference
return resolverMap;
}
}
Complete Example
To show a complete example where a highlight option, with a default highlightAllCallback
option, is used to iterate over the references and add a resolver for the reference.
import { walk, coerceHighlight } from 'graphql-mocks/highlight/utils';
import { setResolver } from 'graphql-mocks/resolver-map';
import { highlightAllCallback } from 'graphql-mocks/resolver-map/utils';
const middleware(options) {
// will either be the given highlight option or fallback to highlighting all
const highlight = coerceHighlight(options?.highlight ?? highlightAllCallback);
return async (resolverMap, packOptions) => {
const graphqlSchema = packOptions.dependencies?.graphqlSchema;
// use references from highlight to iterate over all options
await walk(graphqlSchema, highlight.references, (reference) => {
setResolver(resolverMap, reference, () => 'resolver function!', { replace: true });
});
return resolverMap;
}
}
Useful Utilities
When operating on the landscape of a Resolver Map there are some useful utilities to consider using.
setResolver
import { setResolver } from 'graphql-mocks/resolver-map';
Add a Resolver function to a Resolver Map at a given reference.
getResolver
import { getResolver } from 'graphql-mocks/resolver-map';
Get a Resolver function from a Resolver Map for a given reference.
applyWrappers
import { applyWrappers } from 'graphql-mocks/resolver';
Generally, it's easiest to use embed
and layer
Resolver Map Middleware functions to add wrappers. In other cases it might be useful for a custom Resolver Map Middleware to have an array of wrappers passed in as an option and apply them to a Resolver function using applyWrappers
.