DEV Community

loading...
Cover image for graphQL mutations and input types

graphQL mutations and input types

ajcwebdev profile image anthonyCampolo ・3 min read

An API endpoint that alters data (inserting or altering data) should be a Mutation rather than a Query. The API endpoint becomes part of the top-level Mutation type instead of the top-level Query type.

Let's say we have a “message of the day” server, where anyone can update the message of the day, and anyone can read the current one.

type Mutation {
  setMessage(message: String): String
}

type Query {
  getMessage: String
}
Enter fullscreen mode Exit fullscreen mode

A mutation that maps to a database create or update operation should return the same thing the server stored. If you modify the data on the server, the client can learn about those modifications.

Both mutations and queries can be handled by root resolvers. Here is the root that implements this schema.

var fakeDatabase = {};
var root = {
  setMessage: ({message}) => {
    fakeDatabase.message = message;
    return message;
  },
  getMessage: () => {
    return fakeDatabase.message;
  }
};
Enter fullscreen mode Exit fullscreen mode

You will find a number of different mutations that all accept the same input parameters. Creating an object in a database and updating an object often take the same parameters. You can use “input types” for this, by using the input keyword instead of the type keyword.

Instead of a single message of the day, let's say we have many messages, indexed in a database by the id field. Each message has both a content string and an author string. We want a mutation API both for creating a new message and for updating an old message.

input MessageInput {
  content: String
  author: String
}

type Message {
  id: ID!
  content: String
  author: String
}

type Query {
  getMessage(id: ID!): Message
}

type Mutation {
  createMessage(input: MessageInput): Message
  updateMessage(id: ID!, input: MessageInput): Message
}
Enter fullscreen mode Exit fullscreen mode

The mutations return a Message type. The client can get more information about the newly-modified Message in the same request as the request that mutates it.

Input types can't have fields that are other objects, only basic scalar types, list types, and other input types.

Naming input types with Input on the end is a useful convention, because you will often want both an input type and an output type that are slightly different for a single conceptual object.

Here's some runnable code that implements this schema, keeping the data in memory:

var express = require('express');
var { graphqlHTTP } = require('express-graphql');
var { buildSchema } = require('graphql');

var schema = buildSchema(`
  input MessageInput {
    content: String
    author: String
  }

  type Message {
    id: ID!
    content: String
    author: String
  }

  type Query {
    getMessage(id: ID!): Message
  }

  type Mutation {
    createMessage(input: MessageInput): Message
    updateMessage(id: ID!, input: MessageInput): Message
  }
`);

class Message {
  constructor(id, {content, author}) {
    this.id = id;
    this.content = content;
    this.author = author;
  }
}

var fakeDatabase = {};

var root = {
  getMessage: ({id}) => {
    if (!fakeDatabase[id]) {
      throw new Error('no message exists with id ' + id);
    }
    return new Message(id, fakeDatabase[id]);
  },
  createMessage: ({input}) => {
    var id = require('crypto').randomBytes(10).toString('hex');

    fakeDatabase[id] = input;
    return new Message(id, input);
  },
  updateMessage: ({id, input}) => {
    if (!fakeDatabase[id]) {
      throw new Error('no message exists with id ' + id);
    }
    fakeDatabase[id] = input;
    return new Message(id, input);
  },
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000, () => {
  console.log('Running a GraphQL API server at localhost:4000/graphql');
});

Enter fullscreen mode Exit fullscreen mode

To call a mutation, you must use the keyword mutation before your GraphQL query. To pass an input type, provide the data written as if it's a JSON object. With the server defined above, you can create a new message and return the id of the new message.

mutation {
  createMessage(input: {
    author: "andy",
    content: "hope is a good thing",
  }) {
    id
  }
}
Enter fullscreen mode Exit fullscreen mode

You can use variables to simplify mutation client logic just like you can with queries. For example, some JavaScript code that calls the server to execute this mutation is:

var author = 'andy';
var content = 'hope is a good thing';
var query = `mutation CreateMessage($input: MessageInput) {
  createMessage(input: $input) {
    id
  }
}`;

fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
  body: JSON.stringify({
    query,
    variables: {
      input: {
        author,
        content,
      }
    }
  })
})
  .then(r => r.json())
  .then(data => console.log('data returned:', data));
Enter fullscreen mode Exit fullscreen mode

Discussion

pic
Editor guide