The ability to do mutations is one of the primary features of a GraphQL client. A mutation looks like this:
mutation {
createAuthor(
_id: "john",
name: "John Carter",
twitterHandle: "@john"
) {
_id
name
}
}
You can pass multiple mutations in the same query, as well. If you want to pass the arguments as variables, you have to specify them after the mutation keyword, and then also indicate where those variables should be used:
mutation createAuthor($_id: String, $name: String, $twitterHandle: String) {
createAuthor(
_id: $_id,
name: $name,
twitterHandle: $twitterHandle
) {
_id
name
}
}
// Need to send these variables
{
_id: "john",
name: "John Carter",
twitterHandle: "@john"
}
As you can see, this is quite verbose, with the names of the variables repeated roughly four times. There are a couple ways to solve this:
https://facebook.github.io/relay/graphql/mutations.htm
mutation createAuthor {
createAuthor(input: $input) {
clientMutationId,
status {
_id
name
}
}
}
// Need to send these variables
{
input: {
_id: "john",
name: "John Carter",
twitterHandle: "@john"
}
}
This is simpler because the names of the arguments are only listed once. However, you need a specific input type for every mutation.
But the spec is another restriction on your GraphQL server.
client.mutate(`
newFilm: createMovie(
title: "Star Wars: The Force Awakens",
director: "J.J. Abrams",
producers: [
"J.J. Abrams", "Bryan Burk", "Kathleen Kennedy"
],
releaseDate: "December 14, 2015"
) {
...${filmInfo}
}
`).then(response => {
console.log(response.newFilm);
});
They save you from writing the mutation X thing. It works with any GraphQL server. The downside is, you can't actually pass variables because you would need to know the types.
I don't think we need to impose a spec on the server for mutations. For a low-level API, let's just implement the standard mutation API from GraphQL itself, with all of the boilerplate. To eliminate it, we probably need to know the types of the variables, which is not optimal.
This is exactly what Jonas ended up with in his example todos app:
client.mutation( {
mutation: `mutation makeListPrivate($listId: ID!){
makeListPrivate(id: $listId)
}`,
args: { 'listId': listId }
});
Let's explore additional features for this in the future, there's probably some nice stuff we can do to reduce boilerplate. And maybe we want a switch to support the Relay mutation spec.
I have a question that is more about GraphQL mutations than about Apollo specifically: does the GraphQL/Apollo model allows to chain mutations, by using the result of a mutation A as the parameter of a mutation B without sending the intermediate data back and forth to the client? (clearer explanations of what I’m talking about in the “Time Travel! (Promise Pipelining)” of https://capnproto.org/rpc.html).
After a bit of googling I don’t think GraphQL allows promise pipelines. Sorry for the off-topic.
Yeah, unfortunately it doesn't allow you to pipeline mutations.
COULDN'T IT THOUGH?
what about this magic:
mutation myMutation {
myOtherMutation {
actualResult
}
}
You would be able to access the result of myMutation in the context of myOtherMutation!?
Hum but you would need to provide the result of the first mutation as a GQL _argument_ of the second mutation, so something like that:
mutation {
createAuthor(name: $name) {
_id,
name,
mutation {
createFirstPost(authorId: _id) {
post {
_id,
title
}
}
}
}
}
{
name: "Sashko"
}
But I doubt that this is really possible. (Warning: I don’t understand all the boilerplate involved in GQL mutations and I have no idea what I’m writing).
Actually I wonder if Apollo needs to use GQL mutations at all. It seems to me that one of the specificity of GQL mutations is to request a list of fields that you expect to be modified by the mutation, at the same time that you are calling the mutation.
Now with Apollo the server side code is allowed to invalidate a dependency key and the invalidation server then notify the client. So couldn’t we rely on that for mutations? Basically a mutation would be a function on the server that mutates some server state and invalidates the corresponding dependencies keys that the client could then use to refresh the modified data.
I’m sure we could save one roundtrip here, but I feel like the Apollo invalidation server would be responsible for that, and not the GraphQL mutation query.
And it’s how it works with Meteor by the way. If you create a method that creates a new post:
Meteor.methods({
createPost(title, content) {
check(title, String);
check(content, String);
Posts.insert({title, content});
}
});
the post isn’t returned by the method. The client is still notified of the new post thanks to the reactivity layer— as the insert call notify the mergebox/invalidation server. The method is still free to return something, for instance an error if something went wrong, but the actual data refresh is handled by the invalidation server. Shouldn’t we use the same architecture in Apollo?
I think we should first support all normal GraphQL servers, and then extend it to avoid having to manually refetch data. But I agree having to list the fields to be refetched is a drag.
If this client only works with special GraphQL servers that will be a really big damper on adoption, and kind of defeats the purpose of using a standard like GraphQL.
@stubailo I personally don't like the Relay API fwiw. I prefer to be able to read the actions from the client side to know what is going on in the operation. The Relay spec in the example hides the knowledge that I need to send twitterHandle.
However I think we can pass objects as arguments and access them:
client.mutation({
mutation: `mutation createAuthor($author: Object) {
createAuthor(
_id: $author.id,
name: $author.name,
twitterHandle: $author.twitterHandle
)
}`,
args: {
author: {
_id: "john",
name: "John Carter",
twitterHandle: "@john"
},
}
});
In this case, to avoid needing a unique type, we _could_ infer the types from what is sent and expand into our own prefixed variables since the server shouldn't care what the argument names are right?
Yeah, that looks great!
we could infer the types from what is sent and expand into our own prefixed variables since the server shouldn't care what the argument names are right?
Explain more? Sounds intriguing but I don't quite understand.
Basic mutation support with variables works now!
@stubailo will apollo client support Relay-Mutation?
@agasvina can you open a new issue with more details? What do you mean by this - can you give an example of what you are trying to do?
@stubailo I think @agasvina mean Relay Input Object Mutations Specification you mention above.
mutation createAuthor {
createAuthor(input: $input) {
clientMutationId,
status {
_id
name
}
}
}
// Need to send these variables
{
input: {
_id: "john",
name: "John Carter",
twitterHandle: "@john"
}
}
Which I'm curious too, can I pass input Object like Relay via Apollo?
Thanks
@katopz Seems we can just pass a type into mutation.
I don't know whether 'input' keyword is necessary, seems always using 'type' is enough, just like what we can do in the typescript.
Oh, I was wrong, using 'type' keyword means there should be a resolve function for it, where 'input' means it just does some type check.
I think the scalar keyword is for this usage: http://dev.apollodata.com/tools/graphql-tools/scalars.html#JSON-as-a-scalar
Any updates as to support for input objects? If so can someone point me in the right direction of an example? Thanks!
@jbaxleyiii Did you reference this issue in your updateQueries commit by mistake? If it's not a mistake, then how is that a fix for this issue?
Most helpful comment
Any updates as to support for input objects? If so can someone point me in the right direction of an example? Thanks!