Graphql-js: Nested mutations

Created on 18 Jan 2017  路  11Comments  路  Source: graphql/graphql-js

Say I want to do the following:

mutation {
  updateStudent(studentId: 5, input: $input) {
    firstname,
    lastname,
    studentTags {
      value
    }
  }
}

where $input =

{
  firstname: 'joe',
  lastname: 'cool',
  studentTags: [{
    value: 'summer 2016'
  }]
}

My Student input definition is as follows:

new GraphQLInputObjectType({
  name: 'StudentInput',
  fields: {
    firstname: {
      type: GraphQLString
    },
    lastname: {
      type: GraphQLString
    },
    email: {
      type: GraphQLString
    },
    studentTags: {
      type: new GraphQLList(StudentTagInput)
    }
  }
});

... and my mutation definition looks like this:

export default {
  type: StudentObject,
  args: {
    studentId: {
      type: new GraphQLNonNull(GraphQLInt)
    },
    input: {
      type: StudentInput
    }
  },
  resolve(_, {studentId, input}, context) {
    return ACL.Common.enforceLoggedIn(context.user)
      .then(() => Models.Student.update(context, studentId, input))
  }
}

Is there any way to ease the concept of nested mutations in this case? Almost like having a resolve function attached to the StudentTagInputObject such that it runs whenever a student mutation happens, or having non-root mutations?

The only way I can see how to implement this is defining how the nested mutation will work within the resolve function of the root mutation directly but could lead to duplicated code.

Thanks in advance! Hopefully my question makes sense

Most helpful comment

There's https://github.com/facebook/graphql/issues/252 which is about writing something like:

mutation {
  updateStudent(studentId: 5, input: $input) {
    addTags(tags: $tags)
  }
}

All 11 comments

Can you clarify what you mean by nested mutations?

From what I can tell, this code looks like it should work as written. Could you explain what happened which was surprising and what you would have expected instead?

@leebyron sorry for not being more clear!

ive had time to kind of organize the thoughts in my head. what i'm looking for ideally is having a resolver at each node of the mutation (the input) itself, similar to how queries with nested relations work with multiple resolve functions each at different nodes.

does that make sense?

I'm not sure if I follow. I believe mutations behave as you describe today - each mutation field is backed by a resolver which is responsible for carrying out the mutation.

@leebyron im still fairly new to graphql so please bear with possibly my poor understanding and usage of graphql verbage :(

suppose i have this mutation:

mutation {
  updateStudent(studentId: 5, input: $input) {
    ...
  }
}

where $input =

{
  firstname: 'joe',
  lastname: 'cool',
  studentTags: [{
    value: 'summer 2016'
  }]
}

The updateStudent mutation has a resolver attached to it. however, notice in the $input I have a nested object that will add studentTags to the student. what i'm wondering is whether its possible to have a resolver specifically for the studentTags relation on the $input, similar to how queries are resolved in a n+1 way.

There's https://github.com/facebook/graphql/issues/252 which is about writing something like:

mutation {
  updateStudent(studentId: 5, input: $input) {
    addTags(tags: $tags)
  }
}

Ah I think I see what you're after. GraphQL doesn't work exactly as you describe, though you can still achieve what you're after.

Input types (via a variable in your example) represent pure data and not operations on the server. That allows you to provide that data from a client and know that it is well formed. Then on the server you will get that data correctly shaped into your mutation. For this example, you will get the data exactly as you've shaped it (provided that the input type aligns) into the resolver for your "updateStudent" mutation field. Within that resolver you can see that the "studentTags" field was provided on the input and then incorporate that into your mutative action "under the hood".

I should add that if there are multiple mutations that have the ability to reference "studentTags" in this way, you might consider creating some reusable functions in your implementation, that way your mutation field resolver functions are very simply calling into your internal library code rather than duplicating logic.

Another approach might be to separate "updateTags" or something like it into its own mutation. If the goal is to compose them separately in your API, it does not seem like the updating of tags is particularly dependent on updating other properties of the student. However I also think your API looks quite reasonable as you've designed it presently.

thanks @stubailo ! thats what im asking about :)

@leebyron thanks for taking the time to understand the problem / situation!

@stringbeans As lee is pointing out, supporting nested mutations within the current GraphQL spec is certainly possible. For a bit of flavour you can check out how we generate nested mutations for all our projects: https://blog.graph.cool/improving-dx-with-nested-mutations-af7711113d6#.hsni6ntd3

@sorenbs your link results in a 402 error. I was able to find, I think, the article you where referring to here: https://blog.graph.cool/improving-dx-with-nested-mutations-698c508339ca

I believe my package is the best way to achieve nested mutations.
Check it out https://github.com/oney/graphql-mutate

Was this page helpful?
0 / 5 - 0 ratings