Spring for GraphQL: Mutation

In earlier articles, we talked about GraphQL queries. But, GraphQL is not just about queries.

Like any other API platform, a GraphQL service also needs to provide a way to manipulate data. This is where GraphQL mutation comes into the picture.

In theory, we can use GraphQL queries to modify the state but this will confuse our API consumers.

In REST, we use GET request to retrieve data, and by convention, it should not cause any side-effect. Similarly, we should not use GraphQL queries to modify the application’s state.

Therefore, if we need to provide APIs that change state, we should use mutation instead.

Read more about GraphQL…

What is GraphQL Mutation?

In GraphQL, a mutation is used to insert, update or delete data. The mutation API is defined with the type Mutation rather than the Query.

GraphQL Mutation

An example of a mutation that adds a book to the book catalog.


type Mutation {
    addBook(name: String!, author: String!, publisher: String!,price: Float!): String!
}

In the above example, addBook API is a mutation; it allows you to add/save a book and returns the ID of the book after a successful save.

! means a required field.

Similarly, we can define API to update the book as:


type Mutation {
    updateBook(id: ID!, name: String, author: String, publisher: String, price: Float): String!
}

And, to delete the book:


type Mutation {
    deleteBook(id: ID!): String!
}

We can even design APIs to return info about added, updated, or deleted books as:


type Mutation {
    addBook(name: String!, author: String!, publisher: String!,price: Float!): BookInfo!
    updateBook(id: ID!, name: String, author: String, publisher: String,price: Float): BookInfo!
    deleteBook(id: ID!): BookInfo!
}

Where type BookInfo is defined as:


type BookInfo {
    id : ID
    name : String
    author: String
    publisher: String
    price: Float
}

In GraphiQL, you can add a book using mutation as:

Add Book Mutation

GraphQL Mutation Input Type

Instead of defining the API with scalar arguments, for example – addBook(name: String!, author: String!, publisher: String!,price: Float!), you can define a complex object called input type. This is useful if you want to reuse the input type for both updates and inserts.

An input type is defined with a keyword input instead of type as:


input BookInput {
    name : String
    author: String
    publisher: String
    price: Float
}

As a result, we can change the API definition as:


type Mutation {
    addBook(book: BookInput!): BookInfo!
    updateBook(id: ID!, book: BookInput!): BookInfo!
    deleteBook(id: ID!): BookInfo!
}

And, in GraphiQL (or other clients), we can call API as:

Implementing Mutations in Spring

In Spring for GraphQL, we can implement mutation using @SchemaMapping or @MutationMapping.

If we have defined addBook API as:


type Mutation {
    addBook(name: String!, author: String!, publisher: String!,price: Float!): String!
}

Then, we can implement mutation by defining @SchemaMapping with typeName as Mutation as:


@SchemaMapping(typeName = "Mutation", field = "addBook")
public String addBook(
    @Argument String author,
    @Argument String name,
    @Argument String publisher,
    @Argument Double price) {
  log.info("Saving book, name {}", name);
  var book = new BookInput(name, author, publisher, price);
  return bookCatalogService.saveBook(book);
}

Generally, we can leave the parameter field if the method name is the same as the field, as:


@SchemaMapping(typeName = "Mutation")
public String addBook(
    @Argument String author,
    @Argument String name,
    @Argument String publisher,
    @Argument Double price) {
  log.info("Saving book, name {}", name);
  var book = new BookInput(name, author, publisher, price);
  return bookCatalogService.saveBook(book);
}

Similar to @QueryMapping, Spring also provides a shorthand annotation as @MutationMapping. As a result, we can write addBook mutation as:


@MutationMapping
public String addBook(
    @Argument String author,
    @Argument String name,
    @Argument String publisher,
    @Argument Double price) {
  log.info("Saving book, name {}", name);
  var book = new BookInput(name, author, publisher, price);
  return bookCatalogService.saveBook(book);
}

As always, we can test the API in GraphiQL at http://localhost:8080/graphiql?path=/graphql

Implementing Mutation with Input Type

If a mutation is defined as


type Mutation {
    addBook(book: BookInput!): BookInfo!
}

Then, we can implement mutation by defining @MutationMapping and @Argument as:


@MutationMapping
public BookInfo addBook(@Argument BookInput book) {
  log.info("Saving book, name {}", book.name());
  return bookCatalogService.saveBook(book);
}

Code Example

The working code example of this article is listed on GitHub . To run the example, clone the repository, and import graphql-spring-mutation as a project in your favorite IDE as a Gradle project.

The code use Spring JPA to store data in the in-memory H2 database. You can find more information in READMe.md

Conclusion

GraphQL mutation is used to change the state of the application (insert, update and delete). In Spring for GraphQL, a mutation can be implemented by defining a handler method and using annotation @MutationMapping or @SchemaMapping with the parameter typeName as Mutation.

Social Share !
Pankaj
Pankaj

Software Architect @ Schlumberger ``` Cloud | Microservices | Programming | Kubernetes | Architecture | Machine Learning | Java | Python ```