GraphQL concepts I wish someone explained to me a year ago

Part 4: Mutations (server implementation)

Naresh Bhatia
Naresh Bhatia
5 min readDec 24, 2018

--

Image by Rostyslav

In part 3 of this series we implemented a React app that could send GraphQL queries to our Bookstore server. In this part, we jump back to the server side and implement mutations.

One part will be released every day this week. Follow me or subscribe to my newsletter to get updates on this series.

Source Code

You can download the code for this part by executing the following commands. Skip the first command if you have already cloned the repository.

git clone https://github.com/nareshbhatia/graphql-bookstore.git
cd graphql-bookstore
git checkout 4-mutations-server

Test Drive

Before diving into the implementation, let’s look at how the final implementation works. Make sure you have yarn installed on your machine. Now execute the following commands:

cd apollo-bookstore-server
yarn
yarn dev

This will start the server. Point your browser to http://localhost:4000/ — it will show the GraphQL Playground. You have already used the Playground in part 2, so it needs no introduction.

Let’s start by creating a new author. Enter the following mutation in the left panel of the Playground.

mutation CreateAuthor($name: String!) {
createAuthor(name: $name) {
id,
name
}
}

This mutation introduces the concept of query variables. Instead of hard coding the name of the author in the mutation, we will pass it as a query variable — that’s what $name is. So how do we do that? Click on the QUERY VARIABLES button at the bottom of the Playground. It will pop up a panel where you can enter query variables. Enter the $name variable as follows:

{
"name": "Walter Isaacson"
}

Now click on the Execute button to execute the mutation. You should see the result in the right panel, showing the newly created author. Make a note of the author id — it will be a different string for you.

Creating a new author using Playground

The syntax of mutations looks almost identical to queries, the main difference being that they must start with the mutation keyword. The arguments passed into the mutation (in this case name) is all that the server needs to perform the mutation (create, update or delete). As with queries, if the mutation returns an object, you can ask for nested fields. This can be useful for fetching the new state of an object after an update. For example, in an update mutation, we can ask for the author + the books they have written.

Let’s perform a few more mutations to understand the mutation API. Execute the following mutation to create a publisher:

mutation CreatePublisher($name: String!) {
createPublisher(name: $name) {
id,
name
}
}

Supply it with the following query variables:

{
"name": "Simon & Schuster"
}

Heres’ the result I got. Again, note the publisher id you get.

{
"data": {
"createPublisher": {
"id": "g64j1l",
"name": "Simon & Schuster"
}
}
}

Now let’s create a book using the newly created author and publisher. Here’s the mutation for it:

mutation CreateBook($book: BookInput!) {
createBook(book: $book) {
id,
name,
publisher {
name
}
}
}

Here BookInput is not a scalar like we have seen in the other mutations. It’s an input object type, a special kind of object type that can be passed in as an argument. Here’s how we have defined BookInput in our GraphQL schema:

input BookInput {
name: String!
publisherId: ID!
}

Let’s supply BookInput as a query variable in the Playground and click the Execute button. Make sure you enter the publisher id that you got earlier.

{
"book": {
"name": "Steve Jobs",
"publisherId": "g64j1l"
}
}

The result is shown below. We have successfully created a book. Again, note the book id you get.

{
"data": {
"createBook": {
"id": "fswi5p",
"name": "Steve Jobs",
"publisher": {
"name": "Simon & Schuster"
}
}
}
}

Note that this mutation has been designed to create a book with minimal amount of input — the book’s name and the publisher id. You’ll notice the authors have not been specified. So let’s execute another mutation to add an author:

mutation SetBookAuthors($bookId: ID!, $authorIds: [ID!]!) {
setBookAuthors(bookId: $bookId, authorIds: $authorIds) {
id
name
authors {
name
}
}
}

Enter the query variables as follows (make sure you enter the ids that you got):

{
"bookId": "fswi5p",
"authorIds": [
"3a7ae"
]
}

And here’s the result I got — the book Steve Jobs written by Walter Isaacson:

{
"data": {
"setBookAuthors": {
"id": "fswi5p",
"name": "Steve Jobs",
"authors": [
{
"name": "Walter Isaacson"
}
]
}
}
}

Update mutations are very similar, they generally require an additional argument — the identifier of the object you want to mutate. Try them out yourself. Use the extended Bookstore schema as your guide.

Mutation Implementation

Extending type definitions

First off, we need to add mutation type definitions to our schema. Here are the updates to the three type definition modules:

# ----- author.graphql -----
type Mutation {
createAuthor(name: String!): Author!
updateAuthor(authorId: ID!, name: String!): Author!
}
# ----- publisher.graphql -----
type Mutation {
createPublisher(name: String!): Publisher!
updatePublisher(publisherId: ID!, name: String!): Publisher!
}
# ----- book.graphql -----
type Mutation {
createBook(book: BookInput!): Book!
updateBook(bookId: ID!, book: BookInput!): Book!
setBookAuthors(bookId: ID!, authorIds: [ID!]): Book!
}

input BookInput {
name: String!
publisherId: ID!
}

Adding mutation resolvers

Next we need to add resolvers for the new mutation operations. Here’s an example for the author mutations:

Mutation: {
createAuthor(parent, args) {
return dataSources.bookService.createAuthor({
name: args.name
});
},
updateAuthor(parent, args) {
return dataSources.bookService.updateAuthor(args.authorId, {
name: args.name
});
}
}

Of course we’ll need to add new methods to bookService to support the mutations. For example:

createAuthor(authorInput) {
const author = Object.assign({}, authorInput, { id: newId() });
authors.push(author);
return Promise.resolve(author);
}

updateAuthor(authorId, authorInput) {
let author = findAuthor(authorId);
author = author ? Object.assign(author, authorInput) : null;
return Promise.resolve(author);
}

Summary

We have now covered the mutation implementation on the server. This is what we learned:

  • Mutation syntax and its similarities to query syntax
  • The concepts of query variables and input object types
  • Adding mutation type definitions to the schema
  • Writing resolvers for mutations

Have you gotten stuck at any point so far? I would love to get your questions and comments.

In part 5, we will implement mutations in our React client.

Read Part 5: Mutations (client implementation) >

Resources

--

--