question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Thank you so much for all the work you’ve done to build Graphene!

I’m currently building my GraphQL API using Graphene and one of the problems I’m trying to solve is mutation organization. I expect my application to have hundreds of mutations and naturally there’s a need to break them all down into groups somehow. I read this article (https://medium.freecodecamp.org/organizing-graphql-mutations-653306699f3d) that suggests creating “ops” objects that would work as services that cover particular areas of the application. So, I’d like to be able to structure my queries like this:

mutation myMutation {
  UserOps {
    createUser(input: {
      firstName: "Foo",
      lastName: "Bar",
    }) {
      user {
        id
      }
    }
  }
}

I can’t find a way to implement this. I tried defining userOps as an ObjectType and adding createUser and updateUser as fields, but this approach doesn’t seem to work. Would you have any suggestions on how to approach this problem?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:8 (2 by maintainers)

github_iconTop GitHub Comments

7reactions
dvndrsncommented, May 15, 2019

This is a clever idea, but is not recommended in GraphQL best practices.

In GraphQL spec, the Root Mutation is treated differently than the Root Query and nesting fields break some of the promises that mutations should adhere to. I believe nesting fields under the root mutation actually break the GraphQL spec itself (your API may not be smarter compatible with GraphQL clients like Relay). I’ll try to convince you to not do this, then show you how to do it anyway lol.

In the Root Query, there is no guarantee of order that fields will be resolved in - everything is asynchronous. This is fine though because we promise not to change any data in our resolvers, so we won’t face any problems - we’re just retrieving data that already exists.

In the Root Mutation, GraphQL guarantees that each field will be resolved in the order which they are requested. This is super important while changing data because we may have many dependent mutation fields being requested in one mutation operation. This is important for maintaining consistency between our server and the clients caching responses from our server.

In this example, we know the author name will always be updated with ‘Joe’ since it is the last being requested in a series of fields requested from the Root Mutation.

mutation changeTheName {
  first: updateAuthor(name: "Jim") { author { name } }
  second: updateAuthor(name: "Bob") { author { name } }
  third: updateAuthor(name: "Joe") { author { name } }
}

In this example, the quickest update will win! If resolved asynchronously, we have no idea which will be set because we are outside of the guarantee of the root mutation.

mutation nestedChangeTheName {
  author {
    first: update(name: "Jim") { author { name } }
    second: updateAuthor(name: "Bob") { author { name } }
    third: updateAuthor(name: "Joe") { author { name } }
  }
}

You can ignore this best practice, but without that guarantee of order of execution you may end up in a hard to debug situation in the future. Your API may be breaking GraphQL spec promises and may not work properly with some clients. That being said, you may be OK if you’re only requesting one mutation field per operation. You also may be OK if your mutation code is blocking, but I’m not sure how Graphene will resolve these in practice.

Ok, now that I’ve tried to convince you not to do this, this is how you can do this, if you accept the risks. Pretty simple. No need to use a dict, but you do need a resolver. If you want to skip writing the resolver, you could subclass graphene.Field to return the instance of your object type but I’ll leave that to you if you want to go down this path.

class StoryOperations(graphene.ObjectType):
    create = CreateStory.Field()
    update = UpdateStory.Field()
    delete = DeleteStory.Field()

class Mutation(graphene.ObjectType):
    create_story = CreateStory.Field()
    update_story = UpdateStory.Field()
    delete_story = DeleteStory.Field()
    story = graphene.Field(StoryOperations)

    @staticmethod
    def resolve_story(root: None, info: graphene.ResolveInfo):
        return StoryOperations()

Here’s a blog post that goes into some of the gory details about potential problems you may face (Js-centric):

https://medium.freecodecamp.org/beware-of-graphql-nested-mutations-9cdb84e062b5

0reactions
shinhermitcommented, Apr 21, 2020

@dvndrsn thanks a lot for your answer. I wasn’t sure if I should comment a closed issue, please let me know if I should do otherwise.

From your answer I understood why we shouldn’t use nested mutations. Please could you give us a hint on how to satisfy a use case where nested mutations were considered (using instead sequential mutations) ? If you allow me, I would like to present a use case, then the solution I imagine, hoping you could tell me if it’s good practice, and if not what you recommend.

Let’s image I have User and Group entities, and I want, from the client form to update a group, to be able to not only add a user, but also create a user to be added in a group if the user does not exist. The users have ids named uid (user id) and groups gid (groupd id), just to highlight the difference. So using root mutations, I imagine doing a query like:

mutation {
    createUser(uid: "b53a20f1b81b439", username="new user", password="secret"){
        uid
        username
    }

    updateGroup(gid="group id", userIds=["b53a20f1b81b439", ...]){
        gid
        name
    }
}

You noticed that I provide the user id in the input of the createUser mutation. My problem is that to make the updateGroup mutation, I need the ID of the newly created user. I don’t know a way to get that in graphene inside the mutate methods resolving updateGroup, so I imagined querying a UUID from the API while loading the client form data. So before sending the mutation above, at the initial loading of my client, I would do something like:

query {
    uuid

    group (gid: "group id") {
        gid
        name
    }
}

Then I would use the uuid from the response of this query in the mutation request (the value would be b53a20f1b81b439, as in the the first scriptlet above).

What do you think about this process ? Is there a better way to do that ? Is Python uuid.uuid4 safe to implement this ?

Thanks in advance, Kind regards.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Beware of GraphQL Nested Mutations! - freeCodeCamp
The “problem” is that GraphQL executes a Nested mutation, returning an object with further mutation methods. Unfortunately, once that object is ...
Read more >
Supporting opt-in nested mutations in GraphQL
Discover the benefits of offering nested mutations as an opt-in feature in your GraphQL server, including a slimmer, more manageable schema.
Read more >
Nested Mutations - Lighthouse PHP
# Nested Mutations ... Lighthouse allows you to create, update or delete models and their associated relationships, all in one single mutation. This...
Read more >
Graphql with nested mutations? - Stack Overflow
I am trying to figure out how to mutate a nested object with graphql mutations, if possible. For instance I have the following...
Read more >
GraphQL nested mutations with Apollo: small fatigue, max gain
GraphQL nested mutations with Apollo: small fatigue, max gain. The Scene. I am doing some experimentation and I want to try something with...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found