GraphQL.com

LearnTutorialsCommunityEventsFAQs  

Table of contents

    GRAPHQL BASICS

  • What is GraphQL?
  • The Query
  • Introducing Types
  • Scalars, Objects, and Lists
  • Nullability
  • Querying between Types
  • Schema
  • Enums
  • Interfaces & Unions
  • Arguments
  • MutationsIntroducing mutationsWhy mutations return dataMutation return typesRunning a mutation


Try GraphOS  
The GraphQL developer platform

Mutations

When working with data, it's often not enough just to read out information. Most applications allow users to make changes and update info! GraphQL has a mechanism for changing or writing new values to data called a mutation.

In an everyday sense, we talk about a mutation as something that has changed from what it once was, often in an extreme or fundamentally different way—like an apple mutating to become purple with poisonous green swirls!

Two apples: the first is a regular apple, the second is mutated purple and green.

But when applied to data, the results can be a bit less dramatic: a mutation can be as simple as increasing or decreasing the number of items in stock, submitting a new review to a database, or changing the value of a field. We'll use mutations whenever we want to submit any change to data,

Introducing mutations

To ask for data from a GraphQL service, we need its schema to include a fundamental piece of the puzzle: the Query type, which is considered a root type. A root type is where our interactions with a GraphQL service begin.

Similarly, to change or write data to a GraphQL service, we need the schema to include a new fundamental type: the Mutation type! The Mutation type is also a schema root type because we can use either read or write operations to interact with a GraphQL service.

type Mutation

In a schema, we define the Mutation type right alongside the Query type. It defines its own fields and, like all GraphQL types, includes the type of data it expects to return.

type Query {
fruits: [Fruit!]!
featuredStall: Stall!
orders: [Order!]
fruit(id: ID!): Fruit!
vendor(id: ID!): Stall!
}
type Mutation {
updateProducePrice(produceItemId: ID!, newPrice: Int!): Produce
}

We use the Query type to assess the different requests we can send to retrieve data from a service. In the same way, the Mutation type uses its fields to define the operations we can send to update data. In the above example, the Mutation type has one field: updateProducePrice, which accepts a produceItemId argument of non-nullable type ID and a newPrice argument of non-nullable type Int.

We can imagine this field targeting a particular produce item and update=ing its price to a new value. But why do we see that this field also has a return type of Produce? In other words, why do we expect to receive a Produce type back after calling this field?

Why mutations return data

A best practice when working with mutations is to return the object of data the mutating operation affects. That means if we change something particular in a database, we should receive the thing we updated in response.

But if we are making changes to a particular object—like submitting an order or decreasing an item's quantity by 1—what's the point of getting that updated object back as a response? Isn't our goal to update data, not to read it?

To answer this, we have to factor in the nature of the internet today, and the vast amount of data being exchanged every second. If you consider a website or application with millions of users, it's easy to imagine how many thousands of operations are being sent from moment to moment. Many of these operations concern themselves with reading data: showing a list of products or displaying a user's profile, for example. But many others involve changing data: posting new messages to forums, adding friends to social media networks, sharing pictures, or deducting funds from a bank account, to name a few examples.

To preserve seamless and consistent user experiences, these applications must remain in sync with the changes occurring on the backend. This means that if someone buys the last apple from the supermarket's website, you shouldn't be able to proceed with your purchase of the same product: there are no more apples to buy!

Keeping the frontend of a website in sync with the backend data it relies upon is a daunting and ever-evolving challenge. But one way we can help apps update in real-time is to return the results of changes directly after users have submitted a request. You can see this in action every time you a comment on a thread: when the site receives your comment, it's immediately available to you and other users. In other words, you don't have to refresh the page and make a new query for the most up-to-date list of comments!

Many frontend frameworks support this capability out of the box to keep an app's data—and a user's experience—in sync with its backend data stores. By returning the object that a mutation operates on, we can reflect the updates that have been made immediately and seamlessly in a site or application's user interface.

Mutation return types

The data that we receive in response to performing a mutation has some additional responsibilities. When we try to change data, it's possible that the thing we want to update becomes unavailable in the time it takes to submit the operation. This results in a failed operation—which is very important for the person making the update to know about!

We can see examples of this in action all over the place: imagine trying to place your order for a concert ticket or reserve the last seat at a game. Sometimes the last item in stock gets snapped up by someone else before your request goes through! For this reason, it's useful to include information about whether the request was successful.

Because the response from a Mutation operation contains more than one piece of data, it's common to use a new object type to encapsulate all the fields it should include.

type UpdateProducePriceResponse {
success: Boolean!
message: String!
item: Produce
}

In this UpdateProducePriceResponse type, we see three fields. The success and message fields relate to the status of the network request and whether it went through successfully. The produceItem field returns a Produce type, and we can expect it to contain details about the particular item we performed the operation on. The GraphQL service managing the mutation can gather this information from each request and attach it to the response, whether the updateProducePrice mutation completed successfully.

Using this information, we can enjoy more descriptive and responsive user interfaces. If an error occurs, we can use its success status and message to customize the response we give to users and help them with what steps to take next.

type Mutation {
updateProducePrice(produceItemId: ID!, newPrice: Int!): UpdateProducePriceResponse
}
type UpdateProducePriceResponse {
success: Boolean!
message: String!
produceItem: Produce
}

When writing a mutation, we can simultaneously specify the fields that we want to receive back from its return type. The updateProducePrice mutation returns an UpdateProducePriceResponse type, which contains three fields to choose from: success, message, and produceItem. And because produceItem returns an Produce type, we can also include a selection of subfields to drill even deeper into the update that was made. Let's take a closer look at running mutations in the next section.

Running a mutation

Most mutations need to specify the item that they are making a change to. In the case of a new order, this might be an input containing the order's details: which items are being purchased, from which vendor, and how much the total comes to. We can even include information to augment an order's dollar total, specifying an ID for a particular offer like a discount or a coupon.

We can represent a mutation input that accomplishes this with the type below.

type Mutation {
updateProducePrice(producePriceInput: UpdateProducePriceInput!): UpdateProducePriceResponse
}
input UpdateProducePriceInput {
produceItemId: ID!
newPrice: Int!
}
type UpdateProducePriceResponse {
code: Int!
success: Boolean!
message: String!
produceItem: Produce
}

Because even mutations specify the type of data they return, we can sketch out ahead of time the fields that we want to get in response to our operation. The example below shows the updateProducePrice mutation, which accepts a variable called $producePriceInputVariable and passes it as the value of the producePriceInput argument.

mutation UpdateProducePrice($producePriceInputVariable: UpdateProducePriceInput!) {
updateProducePrice(producePriceInput: $producePriceInputVariable) {
success
message
produceItem {
name
price
}
}
}

The updateProducePrice mutation returns a UpdateProducePriceResponse type, which specifies three fields: success, message, and produceItem.

type UpdateProducePriceResponse {
success: Boolean!
message: String!
produceItem: Produce
}

The mutation operation above shows how even before the operation has been sent, we can select the fields from the object we know we can expect in response.

Try running this mutation in the Apollo Sandbox below. In the Variables panel, you can paste in the following JSON object to set the value of$producePriceInputVariable.

Variables
{
"producePriceInputVariable": {
"produceItemId": "F4",
"newPrice": 113
}
}

Ready to go deeper with your newfound knowledge? Jump into GraphQL architecture by learning about GraphOS.

Up Next: GraphOS  

About

GraphQL.com is maintained by the Apollo team. Our goal is to give developers and technical leaders the tools they need to understand and adopt GraphQL.


GraphQL.com 2024