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.
In this article
Therefore, if we need to provide APIs that change state, we should use mutation instead.
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
.
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:
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 on READMe.md.
Conclusion
GraphQL mutation is used to change the application’s state (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
.
Discussion about this post