What are GraphQL Mutations?
Just like GraphQL Queries provide entry points for reading data, GraphQL Mutations are entry points on a GraphQL server that provides write access to our data sources. Basically, whenever we need to modify data (i.e. create, update, delete data) on your data source, we will do so through GraphQL mutations. Please go through the GraphQL Queries article before reading this article if you are new to GraphQL. In this article, we will cover the fundamentals of GraphQL Mutations.
This is how a basic mutation is defined like in a GraphQL Schema, here we are defining a mutation
that will take userInput
variable of type User!
and returns the newly created User.
# GraphQL Schematype Mutation {createUser(userInput: User!): User}
The structure of a GraphQL Mutation is very similar to a GraphQL Query. Instead of the keyword query, you will be using the keyword mutation in the request. This is how a sample request and response for the above mutation schema will look like
# Mutation Request Sent By Clientmutation CreateUser($userInput: User!) {createUser(userInput: $userInput) {idname}}# Variables{"userInput": {"name": "John Doe","email": "johndoe@hygraph.com"}}# Response{"data": {"createUser": {"id": "1","name": "John Doe","email": "johndoe@hygraph.com"}}}
Again, just like queries, the exact syntax is not fixed and will depend on the implementation of the GraphQL server. The exact syntax will be available in the schema documentation in the API playground.
Types of Mutations
At a high level, there are three types of write operations that we will often come across when wanting to modify data on our data source; Create, Update, and Delete. We will learn from examples throughout this article and use Hygraph’s API playground to fire our mutations. Let us take a small real-world use case and build a HyGraph schema around it.
A Contact Manager system with the following requirements:
- A User can have multiple Contacts
- A Contact can have multiple Labels
- A Label can be used by multiple Contacts
From these requirements we can see that user-contact
is a one-to-many
relationship and contact-label
is a many-to-many
relationship.
We will need 4 models to support this use case:
- User Model
- Contact Model
- Label Model
- Contact Label Model
If you have your own GraphQL server implementation, you can set up your database tables, GraphQL schema, and resolvers and follow along, we would not be exploring that option as it will diverge the scope too much. We will directly use the Hygraph API you can refer to this article to set up the models and create these relationships.
Create Mutation
In our Contact Manager system when we need to create a user, we can define a createUser mutation with input fields for the user's name and email. Here's an example of using a mutation
# Requestmutation createCMUser($cmUser: CmUserCreateInput!) {createCmUser(data: $cmUser) {idname}}# Variables{"input": {"name": "Jane Doe","email": "janedoe@hygraph.com"}}
This mutation creates a user with the name "John Doe" and email "johndoe@example.com" and returns the user's id, name, and email fields.
Update Mutation
In the Contact Manager system, you may want to update the information for an existing contact. This can be achieved using the update mutation. For simplicity, we will avoid passing variables but in a real-world app, always pass variables.
# Requestmutation updateContact {updateCmContact(where: {id: "clgj88ayl367u0bpjcps4in22"}, data: {name: "Bob"}) {idname}}# Response{"data": {"updateCmContact": {"id": "clgj88ayl367u0bpjcps4in22","name": "Bob"}}}
Delete Mutation
Delete mutations are used to delete an existing record from the database. In the Contact Manager system, we may want to delete a contact, label, or user as per needs.
To delete a record, we first need to identify the unique identifier of the record. In GraphQL, this is typically done using the ID scalar type. Once we have the ID, we can pass it as an argument to the delete mutation.
# Requestmutation deleteLabel {deleteCmLabel(where: {id: "clgtm0o8d1ux80bpf82eltzn3"}){idname}}# Response{"data": {"deleteCmLabel": {"id": "clgtm0o8d1ux80bpf82eltzn3","name": "unused label"}}}
Advanced Mutations
Bulk Mutations
Bulk mutations allow us to modify multiple records in a single request. This significantly reduces the number of round-trips between client and server hence improving the efficiency of the application. For example, if you want to make a request wherein you want to update the company name of multiple users that have hygraph.com
as a part of their email, here’s how a bulk mutation for the same would look like
# Requestmutation bulkUpdateUsers {updateManyCmUsersConnection(where: { email_contains: "hygraph.com" }data: { company: "Hygraph" }) {aggregate {count}}}# Response{"data": {"updateManyCmUsersConnection": {"aggregate": {"count": 2}}}}
Nested Mutations
Nested mutations allow you to perform operations on related entities in one request. It allows you to insert/update data in multiple database tables in one client request only so that you do not have to make multiple trips to the server. For example in our contact manager system: We have a one-to-many
relationship between our user-contact
entities, if we want to create a user, two contacts for that user also connect them via the foreign key, you can do it using nested mutations.
The example below creates a new user named John Wick and also creates two contacts named Alice and Bob in Hygraph.
# Requestmutation createUserAndContacts {createCmUser(data: {name: "John Wick",email: "john@test.com",cmContacts: {create: [{ name: "Alice", phone: "789654123" },{ name: "Bob", phone: "123654798" }]}}) {namecmContacts {namephone}}}# Response{"data": {"createCmUser": {"name": "John Wick","cmContacts": [{"name": "Alice","phone": "789654123"},{"name": "Bob","phone": "123654798"}]}}}
Best Practices For GraphQL Mutations
Variables, Named Mutations
Just like queries, we should always use named mutations and variables in our mutations too. Named mutations enable us to debug issues by using the operation name and variables allow us to reuse the mutations by passing different values as required.
Recommended reading
Mutation Design
We saw how to use pre-designed mutations that were generated by Hygraph, but in case you maintain your own GraphQL server and have resolvers on top of it that power your mutations and queries, here are a few points to keep in mind while creating the design for a mutation.
A mutation should respond back with the data that it modified on the data source. This is not mandatory but it makes things much easy for the client as it doesn’t have to query again to ensure the data has changed on the data source. For instance: A mutation to update a user, should take input for updating the user, make the changes in the database and then respond with the new user object.
# Schematype Mutation {UpdateUser(updateUserInput: UpdateUserInput!): User}# Requestmutation updateUser {updateCmUser(data: { name: "John", email: "john@hygraph.com"},where: { id: "clgv03rhk08k70bocb648fydz"}){idname}}# Response{"data": {"updateCmUser": {"id": "clgv03rhk08k70bocb648fydz","name": "John","email": "john@hygraph.com"}}}
A mutation should preferably take in an object instead of multiple scalar fields.
# Recommendedtype Mutation {createUser(createUserInput: CreateUserInput!): User}# Not Recommendedtype Mutation {createUser(name: String!, email: String!, ...otherInputFields): User}
Conclusion
To conclude, GraphQL mutations are the entry points on a GraphQL server that provides write access to data sources. They enable modifications to be made to data sources such as creating, updating, and deleting data. Bulk mutations allow you to make changes to multiple records in a single request, nested mutations allow you to make changes in more than one database entity, and both bulk and nested mutations help improve the efficiency of the application when used correctly. Following best practices of mutations is important to ensure the right mutation design and help with debugging when needed.