Resolvers Composition
Composition tool for GraphQL, with helpers to combine multiple resolvers into one, specify dependencies between fields, and more.
When developing a GraphQL server, it is common to perform some authorization logic on your resolvers, usually based on the context of a request. With Resolvers Composition you can easily accomplish that and still make the code decoupled - thus testable - by combining multiple single-logic resolvers into one.
The following is an example of a simple logged-in authorization logic:
Instead of doing this:
const resolvers = {
Query: {
myQuery(root, args, context) {
// Make sure that the user is authenticated
if (!context.currentUser) {
throw new Error('You are not authenticated!')
}
// Make sure that the user has the correct roles
if (!context.currentUser.roles || context.currentUser.roles.includes('EDITOR')) {
throw new Error('You are not authorized!')
}
// Business logic
if (args.something === '1') {
return true
}
return false
}
}
}
You can do:
const { composeResolvers } = require('@graphql-tools/resolvers-composition')
const resolvers = {
Query: {
myQuery(root, args, context) {
if (args.something === '1') {
return true
}
return false
}
}
}
const isAuthenticated = () => next => (root, args, context, info) => {
if (!context.currentUser) {
throw new Error('You are not authenticated!')
}
return next(root, args, context, info)
}
const hasRole = (role: string) => next => (root, args, context, info) => {
if (!context.currentUser.roles?.includes(role)) {
throw new Error('You are not authorized!')
}
return next(root, args, context, info)
}
const resolversComposition = {
'Query.myQuery': [isAuthenticated(), hasRole('EDITOR')]
}
const composedResolvers = composeResolvers(resolvers, resolversComposition)
composeResolvers
is a method in @graphql-tools/resolvers-composition
package that accepts
IResolvers
object and mappings for composition functions that would be run before resolver itself.
Supported Path Matcher Format
The paths for resolvers support *
wildcard for types and glob patterns for fields. For example:
*.*
- all types and all fieldsQuery.*
- all queriesQuery.single
- only a single queryQuery.{first, second}
- queries for first/secondQuery.!first
- all queries but firstQuery.!{first, second}
- all queries but first/second