Adding Mutations with Express GraphQL

John Au-Yeung - Jan 25 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

We can create a simple GraphQL server with Express. To do this, we need the express-graphql and graphql packages.

In this article, we’ll look at how to create mutations and input types with Express and GraphQL.

Mutations and Input Types

To create mutations, we create a schema that has the Mutation type rather than a Query .

Then it’s a simple as making the API endpoint part of the top-level Mutation type instead of a Query type.

Both mutations and queries can be handled by root resolvers.

We can then create a GraphQL server that takes both queries and mutations as follows:

const express = require('express');  
const graphqlHTTP = require('express-graphql');  
const { buildSchema } = require('graphql');  
const crypto = require('crypto');  
const schema = buildSchema(`  
  input TodoInput {  
    text: String      
  } 

  type Todo {  
    id: ID!  
    text: String      
  } 

  type Query {  
    getTodo(id: ID!): Todo  
  } 

  type Mutation {  
    createTodo(input: TodoInput): Todo  
    updateTodo(id: ID!, input: TodoInput): Todo  
  }  
`);

class Todo {  
  constructor(id, { text }) {  
    this.id = id;  
    this.text = text;  
  }  
}

let todos = {};
const root = {  
  getTodo: ({ id }) => {  
    if (!todos[id]) {  
      throw new Error('Todo not found.');  
    }  
    return new Todo(id, todos[id]);  
  },  
  createTodo: ({ input }) => {  
    const id = crypto.randomBytes(10).toString('hex');  
    todos[id] = input;  
    return new Todo(id, input);  
  },  
  updateTodo: ({ id, input }) => {  
    if (!todos[id]) {  
      throw new Error('Todo not found');  
    }  
    todos[id] = input;  
    return new Todo(id, input);  
  },  
};

const app = express();app.use('/graphql', graphqlHTTP({  
  schema: schema,  
  rootValue: root,  
  graphiql: true,  
}));  
app.listen(3000, () => console.log('server started'));
Enter fullscreen mode Exit fullscreen mode

In the code above, we defined our types by writing:

const schema = buildSchema(`  
  input TodoInput {  
    text: String      
  } 

  type Todo {  
    id: ID!  
    text: String      
  } 

  type Query {  
    getTodo(id: ID!): Todo  
  } 

  type Mutation {  
    createTodo(input: TodoInput): Todo  
    updateTodo(id: ID!, input: TodoInput): Todo  
  }  
`);
Enter fullscreen mode Exit fullscreen mode

We created the input type TodoInput and the Todo type. Then we created the Query type with the getTodo member so that we can get our todo items.

Then in our Mutation , we added the createTodo and updateTodo members so that we can add and update todos.

Then we create our Todo class so that we can store the todo data:

class Todo {  
  constructor(id, { text }) {  
    this.id = id;  
    this.text = text;  
  }  
}
Enter fullscreen mode Exit fullscreen mode

Next, we have our root resolver:

const root = {  
  getTodo: ({ id }) => {  
    if (!todos[id]) {  
      throw new Error('Todo not found.');  
    }  
    return new Todo(id, todos[id]);  
  },  
  createTodo: ({ input }) => {  
    const id = crypto.randomBytes(10).toString('hex');  
    todos[id] = input;  
    return new Todo(id, input);  
  },  
  updateTodo: ({ id, input }) => {  
    if (!todos[id]) {  
      throw new Error('Todo not found');  
    }  
    todos[id] = input;  
    return new Todo(id, input);  
  },  
};
Enter fullscreen mode Exit fullscreen mode

It adds the functions with the same as we specified in our schema so that we can do something when we make some queries.

In this example, getTodo , we’ll return the todo with the given id .The todo that’s found will be returned. Otherwise, we throw an error.

In createTodo , we get the input from the query and then add the todo entry to our todos object, which is our fake database to store the todos. The todo that’s saved will be returned.

Then we have the updateTodo function to update the todo by id . Whatever has the given id will be replaced with the content of input . The todo that’s saved will be returned. We throw an error if a todo with the given id isn’t found.

Then when we go to the /graphql page, we can type in the following to the GraphiQL window:

mutation {  
  createTodo(input: {text: "eat"}) {  
    id  
    text  
  }  
}
Enter fullscreen mode Exit fullscreen mode

Then we get something like:

{  
  "data": {  
    "createTodo": {  
      "id": "c141d1fda69e8d9084bd",  
      "text": "eat"  
    }  
  }  
}
Enter fullscreen mode Exit fullscreen mode

as the response.

If we make a query for updating todo as follows:

mutation {  
  updateTodo(id: "e99ce10750c93793a23d", input: {text: "eat"}) {  
    id  
    text  
  }  
}
Enter fullscreen mode Exit fullscreen mode

We get something like:

{  
  "data": {  
    "updateTodo": {  
      "id": "e99ce10750c93793a23d",  
      "text": "eat"  
    }  
  }  
}
Enter fullscreen mode Exit fullscreen mode

back as the response.

If a todo isn’t found, then we get:

{  
  "errors": [  
    {  
      "message": "Todo not found",  
      "locations": [  
        {  
          "line": 9,  
          "column": 3  
        }  
      ],  
      "path": [  
        "updateTodo"  
      ]  
    }  
  ],  
  "data": {  
    "updateTodo": null  
  }  
}
Enter fullscreen mode Exit fullscreen mode

as the response.

We can make the getTodo query as follows:

query {  
  getTodo(id: "e99ce10750c93793a23d"){  
    id  
    text  
  }  
}
Enter fullscreen mode Exit fullscreen mode

Then we get:

{  
  "data": {  
    "getTodo": {  
      "id": "e99ce10750c93793a23d",  
      "text": "eat"  
    }  
  }  
}
Enter fullscreen mode Exit fullscreen mode

as the response.

Conclusion

We can create mutations as we do with queries.

To accept mutation operations in our GraphQL server, we make our types for storing our data, then we create our mutations by populating the Mutation type with our members.

We can then use the buildSchema function to build the schema we just specified.

Then in our root reducer, we make the functions that with the names that we specified in the type definitions.

Finally, we can make queries to our server to run the mutations.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .