Mutation in GraphQL

4 min read
← Back to Blogs
Mutation in GraphQL

DISCLAIMER: Image is generated using ChatGPT.



In the last post, CPAN and GraphQL, I shared the new CPAN module DBIx::Class::Schema::GraphQL. It was the third post in the series so far. In all the post so far, I have only used CREATE and READ operation. Although the newly created CPAN module does support mutation but I didn’t mention that in the previous post.

John Napiorkowski pointed out exactly that in a Facebook post:

Would love to see an update of this that does full CRUDL

In this post, I will use the same script, mojo-app-v3.pl, from the previous blog post.

For demo purpose we have three authors and each author has one book each, something like below:

...
...
...

$db->resultset('Author')->create({
    name  => "J.R.R. Tolkien",
    books => [{ title => "The Hobbit" }]
});

$db->resultset('Author')->create({
    name  => "Isaac Asimov",
    books => [{ title => "Foundation" }]
});

$db->resultset('Author')->create({
    name  => "Damian Conway",
    books => [{ title => "Perl Best Practices" }]
});

...
...
...

Let’s start the app now:

$ perl mojo-app-v3.pl daemon
[2026-06-08 13:08:34.28544] [22984] [info] Listening at "http://*:3000"
Web application available at http://127.0.0.1:3000

The DBIx::Class::Schema::GraphQL v0.0.1 already provides full CRUDL. However partial updates were not supported.

So to demo the complete feature, I released v0.0.2 and patched the module to support patchX for partial update.

updateX: full update

The intent is that you are supplying the complete new state of the row. You should pass all non-key columns. The resolver takes whatever you provided, builds an update hashref from the defined args, and calls ->update(). However, if you omit a column, the resolver silently skips it. So the database won’t null it out. That means updateX won’t actively destroy data you forget to send, but that behaviour is incidental, not a guarantee you should rely on. The semantic contract is "here is the full row".

mutation {
  updateBook(id: 1, title: "Dune Messiah", author_id: 4) {
    id title
  }
}

patchX: sparse update

The intent is that you are supplying only the columns you want to change. Everything else is deliberately left alone. The resolver applies the same defined-arg filter, but it also enforces that at least one non-key column was provided. If you send only the key and nothing else, it dies with a clear error rather than silently doing nothing.

mutation {
  patchBook(id: 1, title: "Dune Messiah") {
    id title
  }
}

Here author_id is not sent, and that is deliberate, the caller is saying "I only want to rename this book".

Lets go through each one by one using sample data.

[C]reate

$ curl -s -X POST http://127.0.0.1:3000/graphql \
       -H 'Content-Type: application/json' \
       -d '{"query":"mutation { createAuthor(name: \"Frank Herbert\") { id name } }"}'
{
   "data" : {
      "createAuthor" : {
         "id" : 4,
         "name" : "Frank Herbert"
      }
   }
}

[R]ead

$ curl -X POST http://127.0.0.1:3000/graphql \
       -H "Content-Type: application/json" \
       -d '{"query": "{ author(id: 4) { name } }"}'
{
   "data" : {
      "author" : {
         "name" : "Frank Herbert",
      }
   }
}

[U]pdate

Before we update, let’s first fetch the book (id=1).

$ curl -s -X POST http://127.0.0.1:3000/graphql \
       -H 'Content-Type: application/json' \
       -d '{"query":"{ book(id: 1) { title } }"}'
{
   "data" : {
      "book" : {
         "title" : "The Hobbit",
      }
   }
}

Now we will update the above book (id=1) title.

$ curl -s -X POST http://127.0.0.1:3000/graphql \
       -H 'Content-Type: application/json' \
       -d '{"query":"mutation { updateBook(id: 1, title: \"The Hobbit: Revised\", author_id: 1) { title } }"}'
{
   "data" : {
      "updateBook" : {
         "title" : "The Hobbit: Revised",
      }
   }
}

To prove the update was successful, let fetch the same book again.

```bash
$ curl -s -X POST http://127.0.0.1:3000/graphql \
       -H 'Content-Type: application/json' \
       -d '{"query":"{ book(id: 1) { title } }"}'
{
   "data" : {
      "book" : {
         "title" : "The Hobbit: Revised",
      }
   }
}

[P]atch

Let’s update the same book (id=1), partially without providing the author_id.

$ curl -s -X POST http://127.0.0.1:3000/graphql \
       -H 'Content-Type: application/json' \
       -d '{"query": "mutation { patchBook(id: 1, title: \"Dune Messiah\") { title } }"}'
{
   "data" : {
      "patchBook" : {
         "title" : "Dune Messiah"
      }
   }
}

To prove patch worked as expected, let’s fetch the same book (id=1).

$ curl -s -X POST http://127.0.0.1:3000/graphql \
       -H 'Content-Type: application/json' \
       -d '{"query":"{ book(id: 1) { title } }"}'
{
   "data" : {
      "book" : {
         "title" : "Dune Messiah"
      }
   }
}

The distinction maps directly to the HTTP analogy: updateX is PUT, patchX is PATCH.

[D]elete

Now we will delete the author (id=4) we created earlier.

$ curl -s -X POST http://127.0.0.1:3000/graphql \
       -H 'Content-Type: application/json' \
       -d '{"query":"mutation { deleteAuthor(id: 4) }"}'
{
   "data" : {
      "deleteAuthor" : true
   }
}

[L]ist

Finally list all authors.

$ curl -s -X POST http://127.0.0.1:3000/graphql \
       -H 'Content-Type: application/json' \
       -d '{"query":"{ allAuthors { total nodes { name } } }"}'
{
   "data" : {
      "allAuthors" : {
         "total" : 3,
         "nodes" : [
            {
               "name" : "J.R.R. Tolkien"
            },
            {
               "name" : "Isaac Asimov"
            },
            {
               "name" : "Damian Conway"
            }
         ]
      }
   }
}


Happy Hacking !!!