Field Resolvers
Although Vulcan uses SimpleSchema JSON schema to generate a GraphQL schema, it’s important to understand that the two can be very different.
Note: this section will make more sense if you’ve already read the Resolvers section.
Overview
For example, you might have a userId
property in your Movie
schema, but then you want this property to resolve as a user
object in the GraphQL schema (and in turn, in your client store). You can use resolveAs
to accomplish this:
1 | userId: { |
We are doing five things here:
- Specifying that the field should be named
user
in the API. - Specifying that the field can take a
foo
argument that defaults to10
. - Specifying that the
user
field returns an object of GraphQL typeUser
. - Defining a
resolver
function that indicates how to retrieve that object. - Specifying that the original field (
userId
, with typeString
) should also be added to our GraphQL schema.
Note that it is recommended that you pick a different name for the “real” database field (userId
) and the field in your schema (user
), especially if you set addOriginalField
to true
. This way, you will be able to unambiguously require either the userId
or the full user
object just by specifying which field you need.
Also, the resolved type is a GraphQL type, not a primitive type or a schema. Therefore, it must necessarilly be a string ('User'
in the previous example). If you rather need an array of user, the resolved type will be '[User]'
. For primitive types, you must provide the name as a string, instead of the constructor : 'Date'
for a Date
, 'String'
for a String
etc.
Custom Types
Creating a collection with createCollection
will automatically create the associated GraphQL type, but in some case you might want to resolve a field to a GraphQL type that doesn’t correspond to any existing collection.
Here’s how the vulcan:voting
package defines a new Vote
GraphQL type:
1 | import { addGraphQLSchema } from 'meteor/vulcan:core'; |
Which can then be used as the value for type
in a resolveAs
field.
GraphQL-Only Fields
Note that a field doesn’t have to “physically” exist in your database to be able to generate a corresponding GraphQL field.
For example, you might want to query the Facebook API to get the number of likes associated with a post’s URL:
1 | likesNumber: { |
This will create a likesNumber
field in your GraphQL schema (and your Apollo store) even though no such field has to exist in your database.
Note that for GraphQL only fields it is ok to leave out the fieldName
property (which will default to using the same name as the schema field) since there is no actual database field of the same name.
Field Resolvers vs Denormalization
Another approach to achieve the same thing as field resolvers is denormalization, in other words “physically” storing the same information in your database.
For example, assuming you wanted to show a post author’s displayName
, you could write a resolver that fetches the user
object, or you could simply store a new authorDisplayName
property on the post document directly.
So how do you decide which approach to pick? Here are a few general guidelines.
Resolvers are good when:
- The resolver doesn’t require any extra database calls. For example, generating a
createdAtFormatted
date string from acreatedAt
timestamp. - You want to avoid duplicating data. For example, copying a post author’s entire
user
object on the post itself is probably a bad idea because that object can quickly get out of sync with the mainUsers
document. - Keeping the denormalized data up to date gets too complex. If you find yourself writing three or four callbacks to update a single property, a resolver might turn out to be better.
On the other hand, denormalization is better when:
- You want to sort documents by the field. Resolved field don’t actually exist in your database, meaning you can’t sort by them.
- Generating the data through a resolver would require extra database calls.
- The data changes infrequently.