It is often common practice in REST APIs to return a JSON response with an array of objects. In GraphQL we would like to follow this pattern as well. In this article we will go through modifiers, a special group of types which allows us to modify the default behaviour of other types. In GraphQL we deal with various groups of types. These groups are as follows:
- Scalars and custom scalars
- Objects and input object types
- Abstract types - Interfaces and union types
It may be helpful first to go through the articles above. After gaining a fundamental understanding of other types such as scalars and object types you can then move on to modifiers. Next we can start working on the project set-up so that we can test our queries. We assume that npm, git and Node.js versions higher than 8 are already installed on your computer. Now you can execute this command in your shell
install dependencies with
and start the server in development with
Then you can move to GraphQL Playground to execute the queries available in this article. In the model project, we use the in-memory database with fake data for executing our queries.
Let’s first consider this model schema, which was printed with the printSchema function from graphql-js utilities. The model schema in the repository is built with a class-based approach using the graphql-js library. It is often much clearer to view the whole schema written in Schema definition language (SDL). For some time now, SDL has been a part of the specification and it is often used to build the schema itself using the build schema utility or the library called graphql-tools
We can see that we have defined one output object type called User with the following fields: id, username, email, phone, firstName, lastName, createdAt, updatedAt. The id field is typed as an ID scalar and other fields are typed as Strings. We’ve also defined the queries user and users. The user query returns the User object based on the passed id. The users query then returns a list of users. We have also defined the non-required enum type role, which is used in the users query as an argument for filtering the result. In this simple schema we used modifiers quite a lot. In the rest of the article we will go through these use cases.
First let’s formally define modifier. As we have already mentioned, modifier is a special group of type in GraphQL. These types can be defined as follows:
A Modifier modifies the type to which it refers.
From this definition it is clear that we always need to define the type to which we are applying the modifier. In current GraphQL specification, we have these two types of modifiers. Each of the modifier is classified as a separate type:
The List modifier will be our main focus in this article. It will allow us to define if we would like to return a sequence of types. A Non-Null modifier allows us to define if the type/field is required. This can be null (default behaviour in GraphQL) or is required and the GraphQL server raises an error. In this article we will focus mainly on List modifiers and leave a more in-depth discussion of Non-Null modifiers for another article.
By calling query users we expect to return a list of users. Let’s see how this looks when we use the graphql-js library. The queries in our repository are defined as follows:
We can see that we achieve the same functionality as with SDL. The GraphQLList class represents the List. We have applied the instance of this class to the instance of User. Now we are able to fetch the data by executing the users query in GraphQL Playground with the Play button.
We should retrieve this data and obtain users as a list.
The other use case for List modifiers is for designing the createUsers mutation, where we can add users in batch. There are multiple reasons to design the mutations in this way. We may need to add users in transaction, therefore we cannot have a different resolver context or we just want to simplify the API or improve the performance and execute the mutation for multiple users more quickly. This is a great use case for applying the List modifier to our input payload. We can define the input object type just once like this:
or in SDL language
and then apply List modifier to achieve the ability of passing multiple payloads in one input variable.
We can execute the mutation with using inline arguments or if you prefer with using variables
Now let’s go through the rules for result and input coercion. If you are not familiar with these terms, you can take a look at the article on scalars, where we describe input and result coercion.
For the query users, result coercion is relevant for us as we would like to obtain an array of users from the executed query. When we coerce lists, the GraphQL server needs to ensure that the returned data from the resolver function will remain in the same order. The coercion of the each item in the list is then delegated to the result coercion of the referenced type; each item of the array needs to comply to User type or null value. If it returns an object instead of array like in this resolver function:
the GraphQL server should then raise this error
This happens if the coercion of the List modifier does not comply But what happens if some of the items in the list do not coerce properly? In that case we handle the error in a similar manner. We return null instead of the value returned from the resolver function and add an error to the response.
When discussing input coercion of List modifiers we can take into account the createUsers mutation and describe the behavior that raises an error. In contrast to the result coercion, where some items from the result array can be obtained even if one item is not coerced properly, in input coercion we will not be able to execute the whole mutation if one payload cannot be coerced. Let’s take a look at the following example, where we would like to pass a list of two payloads, but one payload does not comply to the input type and does not have the required username field. Upon executing this mutation we receive the following error:
The whole mutation fails even if only the input coercion in the input object type in one item in the list does not comply. However, it is important to emphasize that if we pass null as follows, the whole mutation will be executed. However, this depends on whether or not we applied any additional modifiers and composed the modifiers in a more complex type. We will go through this topic in the last section of this article on Modifier composition.
If we consider the definition of the modifier above, we know that the modifier basically creates a new type from the referenced type with additional functionality. In our case we are adding behavior so that the result coercion will accept a list of items and not just the item itself. This is also similar to higher order functions or the decorator pattern and in the same manner we can chain higher order functions or HOCs in React. We are also able to compose modifiers by applying a modifier to the type where the previous modifier is already applied. We can combine the Non-Null modifier with our List modifier in the following way. This way we basically combine three modifiers, which are chained as follows
This creates a special type. When using only a list modifier we are allowed to return a null value from the resolver. We can even combine the items in the array to contain null values as in this array:
But when we apply the composed modifier as above, we are only allowed to pass the array containing the objects that comply to the User type. The list above will therefore be rejected. The null value returned from resolver will be also rejected. You can take a look at the table below, which contains what each modifier will allow in order to get a better idea of which combinations of modifiers are suitable for different use cases. The only rule in chaining modifiers applies to Non-null modifiers. It declares that we cannot wrap one Non-Null modifier with another Non-Null modifier.
UserObject in this table can be equal for example to
For simplicity, we did not cover differences between input and output coercion for these more complex types. The behaviour is different only as we discussed in the result and input coercion section. If there would be different UserObject, which does not comply to User type coercion (e.g. does not have username property), there would be additional rules.
In this article we have covered one special group of types in GraphQL called Modifiers. With modifiers we are allowed to inject special behaviour into the referenced GraphQL type, add a List and other required fields, and even combine these use cases to build more complex types. Modifiers are a great tool to make elegant GraphQL schemas.