Here's a quick summary of everything we released in Q1 2024.

GraphQL

Mutations

GraphQL Mutations are a GraphQL server's entry points that provide write access to our data sources.

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 Schema
type 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 Client
mutation CreateUser($userInput: User!) {
createUser(userInput: $userInput) {
id
name
email
}
}
# 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

ERDiagram

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

# Request
mutation createCMUser($cmUser: CmUserCreateInput!) {
createCmUser(data: $cmUser) {
id
name
email
}
}
# Variables
{
"input": {
"name": "Jane Doe",
"email": "janedoe@hygraph.com"
}
}

CreateMutation

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.

# Request
mutation updateContact {
updateCmContact(where: {id: "clgj88ayl367u0bpjcps4in22"}, data: {name: "Bob"}) {
id
name
}
}
# 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.

# Request
mutation deleteLabel {
deleteCmLabel(where: {id: "clgtm0o8d1ux80bpf82eltzn3"}){
id
name
}
}
# 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

# Request
mutation 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.

# Request
mutation createUserAndContacts {
createCmUser(
data: {
name: "John Wick",
email: "john@test.com",
cmContacts: {
create: [
{ name: "Alice", phone: "789654123" },
{ name: "Bob", phone: "123654798" }
]
}
}) {
name
cmContacts {
name
phone
}
}
}
# Response
{
"data": {
"createCmUser": {
"name": "John Wick",
"cmContacts": [
{
"name": "Alice",
"phone": "789654123"
},
{
"name": "Bob",
"phone": "123654798"
}
]
}
}
}

NestedMutation.png

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.

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.

# Schema
type Mutation {
UpdateUser(updateUserInput: UpdateUserInput!): User
}
# Request
mutation updateUser {
updateCmUser(
data: { name: "John", email: "john@hygraph.com"},
where: { id: "clgv03rhk08k70bocb648fydz"}
){
id
name
email
}
}
# Response
{
"data": {
"updateCmUser": {
"id": "clgv03rhk08k70bocb648fydz",
"name": "John",
"email": "john@hygraph.com"
}
}
}

A mutation should preferably take in an object instead of multiple scalar fields.

# Recommended
type Mutation {
createUser(createUserInput: CreateUserInput!): User
}
# Not Recommended
type 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.