This article was originally posted on moonhighway.com.
You can't spell GraphQL without the QL: the query language. But don't let the term query
suggest that GraphQL is only about getting data. GraphQL is way more than that.
To change data with GraphQL, we can send a mutation. Think of a GraphQL mutation as a function that can perform custom CREATE, UPDATE, and/or DELETE operations with a little extra finesse and flexibility.
In this article, we'll take a closer look at GraphQL mutations: how to design them in the schema and how to execute them using the GraphQL query language.
Mutations should represent the verbs in your application. They should consist of the things that users should be able to perform with your server. When designing your GraphQL API, make a list of all of the actions that a user can take with your application. Those are most likely your mutations.
The Mutation
is a root object type, just like Query
. Mutations have names. They can have selection sets that return object types or scalars. We define all of the mutations available on our GraphQL API within the Mutation
type in the schema:
type Mutation {
# mutations go here
}
Within the Mutation
type in the schema, we give the mutation by name and define what should be returned from the mutation:
type Mutation {
deleteAllSongs: Boolean!
}
deleteAllSongs
is the name of the mutation. It will return a boolean to describe whether the mutation was successful or not. The verb we want to do is delete all of the songs in the dataset. This mutation is bad news.
To run this mutation, we'll send the following mutation using the GraphQL query language:
mutation Chaos {
deleteAllSongs
}
And we should receive the following response:
{
"data": {
"deleteAllSongs": true
}
}
With that mutation, all of our songs are gone. While we may not feel great about torching all of our data, we should find solace in the fact that we now know how to send a mutation to a GraphQL API, a mutation that returns a boolean value.
Sending Arguments to a Mutation
Let's consider another mutation, but instead of destroying something, let's create something. We'll start with the schema:
type Mutation {
addSong(
title: String!
numberOne: Boolean
performerName: String!
): Song!
}
The name of the mutation is addSong
and takes in three arguments: a non-nullable string for title
, a nullable boolean value for whether the song was a numberOne
hit, and a non-nullable string for performerName
. We can assume that the mutation adds this new song to a database. Notice that we're returning the Song
type from this mutation. Song
is defined in the schema as follows:
type Song {
id: ID!
title: String!
numberOne: Boolean
performerName: String!
}
This means that when we send the mutation, the Song
object will be returned, giving us access to all of the fields on Song
.
mutation CreateSong {
addSong(
title: "Electric Avenue"
numberOne: false
performerName: "Eddy Grant"
) {
title
numberOne
performerName
}
}
The above can be used to create new songs. Because this mutation returns Song
and it is non-nullable, we need add a selection set after the mutation. In other words, the arguments list is followed by a set of curly braces around another list of fields. Here we select the title
and numberOne
fields for the song that was just created.
{
"data": {
"title": "Electric Avenue",
"numberOne": false,
"performerName": "Eddy Grant"
}
}
Sending Arguments as Variables
So far, we've sent mutation arguments inline directly with the query text. It can be difficult to collect data from your applications this way. As an alternative, you could use input variables. Variables replace the static value in the query so that we can pass dynamic values instead.
Let's consider our addSong mutation. Instead of dealing with strings, we'll use variable names which in GraphQL are always preceded by a $
character:
mutation createSong($title: String!, $numberOne: Boolean, $by: String!) {
addSong(title: $title, numberOne: $numberOne, performerName: $by) {
title
numberOne
performerName
}
}
The static value is replaced by a $variable
. Then, we state that the $variable
can be accepted by the mutation. From there, we map each of the $variable
names with the argument name. In GraphiQL or GraphQL Playground, there is a window for Query Variables in the lower left corner. This is where we send the input data as a JSON object. Be sure to use the correct variable name as the JSON key:
{
"title": "No Scrubs",
"numberOne": true,
"by": "TLC"
}
Variables are very useful when sending argument data. Not only will this keep our mutations more organized in a GraphQL Playground test, but allowing dynamic inputs will be hugely helpful later when connecting a client interface.
Returning Custom Objects from a Mutation
So far, we have returned a Boolean
and a Song
object from a mutation. There may be cases where you want to have access to more fields as the result of a mutation. Perhaps a timestamp? Or some data about whether the mutation was successful? You can construct a custom response object type that can deliver those fields. We'll start by returning the AddSongResponse
object in the schema:
type Mutation {
addSong(
title: String!
numberOne: Boolean
performerName: String!
): AddSongResponse!
}
Then we'll create the AddSongResponse
object:
type AddSongResponse {
song: Song!
success: Boolean!
timestamp: String!
}
By creating this type, we can encapsulate the song and some metadata fields about the mutation's execution and return them from the mutation. The query changes a bit with this enhancement:
mutation createSong($title: String!, $numberOne: Boolean, $by: String!) {
addSong(title: $title, numberOne: $numberOne, performerName: $by) {
song {
title
numberOne
}
success
timestamp
}
}
The Song
object fields are now nested under the song
field. song
, success
, and timestamp
are now at the same level. Creating these custom return objects can allow for greater insights into the mutations than just returning a simpler object type.
Mutations start with the schema, and planning out which mutations is an important process. Remember that these mutations are flexible and can return anything: scalar values like booleans or strings, core types like the Song
, or custom mutation response objects.
For more on how to set up a GraphQL server that supports mutations, check out our playlist on egghead.io: Create Fullstack Applications with GraphQL and Apollo.
Top comments (5)
Is it possible to return two "things" from a graphql mutation in like an or statement format?
For example, what if you wanted to return a
song
fromaddSong
, or some sort of "customError" if say that song was already created?The problem I see with a custom object like
AddSongResponse
, is that it cant be reused - it only applies to adding songs. If you had a customError , you could return this from say anaddSong
mutation, anaddArtist
mutation, anaddAlbum
mutation etc etc rather than individual objects for each mutationHey Alex - yeah absolutely. You can return whatever you want from a GraphQL mutation as long as you specify it in the schema. If you wanted to return a reusable object, that's totally doable!
This is a good article about some other options.
Thanks very much for the swift reply!
I had a look at the article you linked, but I seem to be struggling to understand how I return "this or that" from a mutation. My example is as follows:
I'd like to log a user in, and if the user doesn't exist, return a "Custom Error". In my head it would look something like this:
Essentially, we return a User or a CustomError - these are both types defined in the schema. However, I don't know if this is possible (cant get it working at least!).
Tying the return of login down to a "LoginResponse" object feels wrong, because i'll need to have a "-InsertmutationNameHere-Response" object for every mutation. It seems better to have a system of "if we have data, return it or return CustomError" - if thats possible!
Oh gotcha! Look into union types. Unions allow you to return this or that and are often used with Error types.
Your example would probably look like this in the schema:
The docs describe unions in more detail: graphql.org/learn/schema/#union-types
And Sasha Solomon has written about how she's used this pattern at Medium and Twitter: medium.com/@sachee/200-ok-error-ha...
Hey Eve, sorry for not replying. I got this working using your initial idea - returning a custom type :)