2020-01-03
|~3 min read
|542 words
Last month I wrote about how to compose GraphQL schemas with Apollo.
Today, I’ll be extending that example to investigate resolvers. Specifically, writing resolvers that resolve additional nested types.
For simplicity, I’ll use the same Product
type.
As a reminder, the type definition of Product
is:
type Product {
name: String!
price: Float!
image: String!
type: ProductType!
createdBy: User!
description: String
liquidCooled: Boolean
range: String
bikeType: BikeType
}
I also have several queries that enable retriving product data:
extend type Query {
products: [Product]!
product(id: ID!): Product!
}
Simple resolvers to retrieve the data could be written as:
import { Product } from './product.model'
const product = (_, args) => {
return Product.findById(args.id).exec()
}
const products = () => {
return Product.find().exec()
}
export default = {
Query: {
product,
products
},
}
In this case, my Product
is a mongoose model and consequently comes with all of the methods I need to query the database for the data I’m looking for.
Note, however, that each of the Product attribute’s types are not scalar. type
is ProductType
, bikeType
is BikeType
, and createdBy
is a User
.
ProductType
and BikeType
are actually enums. So, they resolve down to strings and are no problem. The createdBy
field, however, isn’t a string, it’s another object.
The type definition of User
is:
type User {
_id: ID!
email: String!
apiKey: String!
role: String!
}
As a result, if I want to be able to know who created the product, I will need to specify it in the resolver for the Product
.
import { Product } from './product.model'
import { User } from '../user/user.model'
const product = (_, args) => {
return Product.findById(args.id).exec()
}
const products = () => {
return Product.find().exec()
}
const createdBy = (product) => {
return User.findById( product.createdBy).lean().exec()
}
export default = {
Query: {
product,
products
},
Product: {
__resolveType(product) {},
createdBy
}
}
This is a good example of when it makes sense to use the first argument in the resolver.1
Per Apollo, the first argument in the resolver is:
The object that contains the result returned from the resolver on the parent field, or, in the case of a top-level Query field, the rootValue passed from the server configuration. This argument enables the nested nature of GraphQL queries.
Whereas the resolver for product
, which has a placeholder _
in the first position, resolving createdBy
actually uses a value.
That’s because product
is a top level query and so receives the rootValue
in the first position of the resolver. The rootValue
, however, is not helpful in finding a product by an ID. The createdBy
resolver, however, receives the Product
as the returned result which is useful in resolving the createdBy
’s User
object.
In most circumstances, the first argument of a resolver is not going to be very useful - particularly if it’s a top-level resolver and receives the rootValue
. However, once you get past that initial tier and you need to resolve the object all the way down, the received result becomes much more useful as this example demonstrates.
1 The GraphQL resolver function signature has four possible arguments. For more information, see the Apollo docs.
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!