In this API docs, a resolver, resolver function, or ResolverFunction
all refer to a function with the same signature as described on this documentation.
Generic resolver composition helpers.
combineResolvers(
...resolvers: Array<ResolverFunction>
): ResolverFunction
Combines resolver functions into one resolver in a first-result-returns manner: all resolvers will be called sequentially with the same initial arguments until one resolves to something other than undefined
; when that happens, the resolved value will be returned and the remaining resolver functions will be ignored.
Useful for things like authorization and access control.
import { graphql } from 'graphql'
import { makeExecutableSchema } from 'graphql-tools'
import { skip, combineResolvers } from 'graphql-resolvers'
const typeDefs = `
type Query {
sensitive: String!
}
schema {
query: Query
}
`
/**
* Sample resolver which returns an error in case no user
* is available in the provided context.
*/
const isAuthenticated = (root, args, { user }) => user ? skip : new Error('Not authenticated')
/**
* Sample resolver which returns an error in case user
* is not admin.
*/
const isAdmin = combineResolvers(
isAuthenticated,
(root, args, { user: { role } }) => role === 'admin' ? skip : new Error('Not authorized')
)
/**
* Sample sensitive information resolver, for admins only.
*/
const sensitive = combineResolvers(
isAdmin,
(root, args, { user: { name } }) => 'shhhh!'
)
// Resolver map
const resolvers = { Query: { sensitive } }
const schema = makeExecutableSchema({ typeDefs, resolvers })
// Resolves with a "Non authenticated" error.
graphql(schema, '{ sensitive }', null, { }).then(console.log)
// Resolves with a "Not authorized" error.
graphql(schema, '{ sensitive }', null, { user: { role: 'some-role' } }).then(console.log)
// Resolves with a sensitive field containing "shhhh!".
graphql(schema, '{ sensitive }', null, { user: { role: 'admin' } }).then(console.log)
pipeResolvers(
...resolvers: Array<ResolverFunction>
): ResolverFunction
Combines resolver functions into one resolver in a piping manner: all resolvers will be called sequentially with the first argument (root
) being the result of the previous resolver, and the rest being the initial provided argument. The last resolved value will be returned.
This is usually something that gets done using GraphQL nesting nature. Sometimes, though, you simply want to reuse logic for things that should not be available on your GraphQL API.
import { graphql } from 'graphql'
import { makeExecutableSchema } from 'graphql-tools'
import { pipeResolvers } from 'graphql-resolvers'
const typeDefs = `
type User {
id: String
name: String
}
type Query {
user: User
}
schema {
query: Query
}
`
/**
* Reusable resolver to get the request object from the context.
*/
const getRequest = (root, args, { req }) => req
/**
* Resolver to get current logged user.
*/
const user = pipeResolvers(getRequest, ({ user }) => user)
const schema = makeExecutableSchema({
typeDefs,
resolvers: {
Query: { user }
}
})
// Resolves with { data: { user: null } }
graphql(schema, '{ user { id } }', null, {}).then(console.log)
// Resolves with { data: { user: { id: 1 } } }
graphql(schema, '{ user { id } }', null, { user: { id: 1 } }).then(console.log)
allResolvers(
resolvers: Array<ResolverFunction>
): ResolverFunction
Combines a resolver array into one resolver that will resolve all in parallel and return and array of resolved values.
GraphQL currently does not support a field to depend on the resolved result of another field. For these situations, graphql-resolvers
provides a suit of methods to allow for execution based field dependencies.
resolveDependee(
dependeeName: string
): ResolverFunction
Factory for a resolver which would resolve a sibling field's value. If dependeeName field is not a Scalar, it will run it's resolver function.
resolveDependee(
dependeeNames: Array<string>
): ResolverFunction
Same as resolveDependees
, but resolves an array of dependees.
resolveDependee(
resolver: ResolverFunction
): ResolverFunction
Wraps a resolver to identify it as being the resolver of a possible dependee field.
Even though this is not mandatory, it will help prevent a resolver to be executed more then once when using the field dependency system.
Having a schema such as...
type Vote {
id: Int
choice: String
}
type Query {
votes: [Vote]
winningChoice: String
}
schema {
query: Query
}
...where winningChoice
field depends on the computed value of it's sibling field votes
, you could write your resolvers as follows:
import R from 'ramda'
import { isDependee, resolveDependee, pipeResolvers } from 'graphql-resolvers'
// Sample data.
const votesData = [
{ choice: 'A', id: 1 },
{ choice: 'B', id: 2 },
{ choice: 'C', id: 3 },
{ choice: 'B', id: 4 },
{ choice: 'C', id: 5 },
{ choice: 'C', id: 6 },
]
// Resolvers
const votes = isDependee(() => votesData)
/**
* Sample resolver for the calculation of winner choice.
*
* From the data above we know 'C' is the winning choice,
* so to simplify things we will resolve it statically.
*/
const winningChoice = pipeResolvers(
resolveDependee('votes'),
votes => {
// ...computed winning choice from array of votes.
return 'C'
}
)
export const resolvers = {
Query: {
votes,
winningChoice,
}
}