Nested GraphQL Mutations with Prisma Photon

Build and configure a Next.js Docker environment for development and production use.

Photon by Prisma, is an amazing package that takes care of all the heavy lifting of performing CRUD operations on your data from the backend side of your full stack web application. We have previously shown how you can use Photon to easily create a GraphQL api [1, 2, 3]. In that example, Users can have many Blogs, but we had to first create a user and then create blogs.

Video of what we are doing:

You might wonder whether there would be a more straightforward way to perform a single nested mutation that would both create the user and then the blogs and it turns out that Photon makes that really easy.

A mutation is a GraphQL term that refers to an operation that changes the data. Here are two mutations that we’ve used in the past for creating Users and Posts.

Create a User

mutation signupUserMutation($name: String!, $email: String!) {
  signupUser(data: { name: $name, email: $email }) {
    ...UserFragment
  }
}

Create a Blog Post

mutation createDraftMutation(
  $title: String!
  $content: String!
  $authorEmail: String!
) {
  createDraft(title: $title, content: $content, authorEmail: $authorEmail) {
    ...PostFragment
  }
}

To create a nested mutation, we use the create method within our posts field of the user creation mutation. We can specify an array of all the posts that we want to add to our new user and Photon, along with the Nexus plugin for Prisma will create both the new user and all the new posts that we desire.

Create a User first, then several Blogs associated to the user

mutation {
  signupUser(
    data: {
      email: "stephen@codemochi.com"
      name: "Stephen"
      posts: {
        create: [
          { title: "First Post", content: "Yay", published: true }
          { title: "Second Post", content: "Oh yeah", published: false }
        ]
      }
    }
  ) {
    id
    name
    email
    posts {
      id
      title
      content
    }
  }
}

With great power comes great responsibility- don't mess it up!
With great power comes great responsibility- don't mess it up!

Remember in Spiderman when they say "with great power comes great responsibility"? Well, that holds true here. Nexus, will auto-generate queries and mutations that we specify. An issue we need to be aware of is that since we are using the stock create user mutation as created by Nexus instead of a custom resolver, this mutation has a lot more flexibility in what it allows for.

In the past we made a custom createDraft mutation that looked like this:

t.field('createDraft', {
  type: 'Post',
  args: {
    title: stringArg(),
    content: stringArg({ nullable: true }),
    authorEmail: stringArg(),
  },
  resolve: (_, { title, content, authorEmail }, ctx) => {
    return ctx.photon.posts.create({
      data: {
        title,
        content,
        published: false,
        author: {
          connect: { email: authorEmail },
        },
      },
    })
  },
})

We opted for doing this instead of simply doing t.crud.createOnePost({ alias: ‘createDraft’ }) because using the long form version, allows us to control exactly which arguments we will accept and how they get passed into the photon create method. In the case of the nested mutation above, we allow the users to enter the boolean value of the published field which allows them to specify whether they want it published or not, and this is explicitly forbidden in the createDraft mutation because we are setting every post’s published field that is created to false.

If you are fine with your user’s having that level of flexibility for that particular model item, then this whole discussion likely won’t matter. If you’d still like to do nested mutations, but you want to limit which fields the user can update, we can create a custom mutation where we limit which arguments we can pass into it. Create a new mutation that looks like this:

t.field('createUserAndDrafts', {
  type: 'User',
  args: {
    name: stringArg(),
    email: stringArg(),
    posts: arg({ type: 'PostCreateManyWithoutPostsInput' }),
  },
  resolve: (_, { name, email, posts }, ctx) =>
    ctx.photon.users.create({
      data: {
        email,
        name,
        posts: {
          create: posts.create.map(({ title, content }) => ({
            title,
            content,
            published: false,
          })),
        },
      },
    }),
})

Here, the mutation will remain unchanged from what we used above in the graphql section, but we are only using the title and content and we are setting our own value for published in the resolver.

Learn something new? Share it with the world!

There is more where that came from!

Drop your email in the box below and we'll let you know when we publish new stuff. We respect your email privacy, we will never spam you and you can unsubscribe anytime.