# Nested Mutations

Lighthouse allows you to create, update or delete models and their associated relationships all in one single mutation.

# Return Types Required

You have to define return types on your relationship methods so that Lighthouse can detect them.

use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    // WORKS
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    // DOES NOT WORK
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

# Partial Failure

By default, all mutations are wrapped in a database transaction. If any of the nested operations fail, the whole mutation is aborted and no changes are written to the database.

You can change this setting in the configuration.

# Belongs To

We will start of by defining a mutation to create a post.

type Mutation {
  createPost(input: CreatePostInput! @spread): Post @create
}

The mutation takes a single argument input that contains data about the Post you want to create.

input CreatePostInput {
  title: String!
  author: CreateAuthorRelation
}

The first argument title is a value of the Post itself and corresponds to a column in the database.

The second argument author, exposes operations on the related User model. It has to be named just like the relationship method that is defined on the Post model.

input CreateAuthorRelation {
  connect: ID
  create: CreateUserInput
  update: UpdateUserInput
}

There are 3 possible operations that you can expose on a BelongsTo relationship when creating:

  • connect it to an existing model
  • create a new related model and attach it
  • update an existing model and attach it

Finally, you need to define the input that allows you to create a new User.

input CreateUserInput {
  name: String!
}

To create a new model and connect it to an existing model, just pass the ID of the model you want to associate.

mutation {
  createPost(input: { title: "My new Post", author: { connect: 123 } }) {
    id
    author {
      name
    }
  }
}

Lighthouse will create a new Post and associate an User with it.

{
  "data": {
    "createPost": {
      "id": 456,
      "author": {
        "name": "Herbert"
      }
    }
  }
}

If the related model does not exist yet, you can also create a new one.

mutation {
  createPost(
    input: { title: "My new Post", author: { create: { name: "Gina" } } }
  ) {
    id
    author {
      id
    }
  }
}
{
  "data": {
    "createPost": {
      "id": 456,
      "author": {
        "id": 55
      }
    }
  }
}

When issuing an update, you can also allow the user to remove a relation. Both disconnect and delete remove the association to the author, but delete also removes the author model itself.

type Mutation {
  updatePost(input: UpdatePostInput! @spread): Post @update
}

input UpdatePostInput {
  title: String
  author: UpdateAuthorRelation
}

input UpdateAuthorRelation {
  connect: ID
  create: CreateUserInput
  update: UpdateUserInput
  disconnect: Boolean
  delete: Boolean
}

You must pass a truthy value to disconnect and delete for them to actually run. This structure was chosen as it is consistent with updating BelongsToMany relationships and allows the query string to be mostly static, taking a variable value to control its behaviour.

mutation UpdatePost($disconnectAuthor: Boolean) {
  updatePost(
    input: {
      title: "An updated title"
      author: { disconnect: $disconnectAuthor }
    }
  ) {
    title
    author {
      name
    }
  }
}

The author relationship will only be disconnected if the value of the variable $disconnectAuthor is true, if false or null are passed, it will not change.

{
  "data": {
    "updatePost": {
      "title": "An updated title",
      "author": null
    }
  }
}

# Has Many

The counterpart to a BelongsTo relationship is HasMany. We will start of by defining a mutation to create an User.

type Mutation {
  createUser(input: CreateUserInput! @spread): User @create
}

This mutation takes a single argument input that contains values of the User itself and its associated Post models.

input CreateUserInput {
  name: String!
  posts: CreatePostsRelation
}

Now, we can an operation that allows us to directly create new posts right when we create the User.

input CreatePostsRelation {
  create: [CreatePostInput!]!
}

input CreatePostInput {
  title: String!
}

You can now create a User and some posts with it in one request.

mutation {
  createUser(
    input: {
      name: "Phil"
      posts: {
        create: [
          { title: "Phils first post" }
          { title: "Awesome second post" }
        ]
      }
    }
  ) {
    id
    posts {
      id
    }
  }
}
{
  "data": {
    "createUser": {
      "id": 23,
      "posts": [
        {
          "id": 434
        },
        {
          "id": 435
        }
      ]
    }
  }
}

When updating a User, further nested operations become possible. It is up to you which ones you want to expose through the schema definition.

The following example covers the full range of possible operations: create, update and delete.

type Mutation {
  updateUser(input: UpdateUserInput! @spread): User @update
}

input UpdateUserInput {
  id: ID!
  name: String
  posts: UpdatePostsRelation
}

input UpdatePostsRelation {
  create: [CreatePostInput!]
  update: [UpdatePostInput!]
  delete: [ID!]
}

input CreatePostInput {
  title: String!
}

input UpdatePostInput {
  id: ID!
  title: String
}
mutation {
  updateUser(
    input: {
      id: 3
      name: "Phillip"
      posts: {
        create: [{ title: "A new post" }]
        update: [{ id: 45, title: "This post is updated" }]
        delete: [8]
      }
    }
  ) {
    id
    posts {
      id
    }
  }
}

# Belongs To Many

A belongs to many relation allows you to create new related models as well as attaching existing ones.

type Mutation {
  createPost(input: CreatePostInput! @spread): Post @create
}

input CreatePostInput {
  title: String!
  authors: CreateAuthorRelation
}

input CreateAuthorRelation {
  create: [CreateAuthorInput!]
  connect: [ID!]
  sync: [ID!]
}

input CreateAuthorInput {
  name: String!
}

Just pass the ID of the models you want to associate or their full information to create a new relation.

mutation {
  createPost(
    input: {
      title: "My new Post"
      authors: { create: [{ name: "Herbert" }], connect: [123] }
    }
  ) {
    id
    authors {
      name
    }
  }
}

Lighthouse will detect the relationship and attach/create it.

{
  "data": {
    "createPost": {
      "id": 456,
      "authors": [
        {
          "id": 165,
          "name": "Herbert"
        },
        {
          "id": 123,
          "name": "Franz"
        }
      ]
    }
  }
}

It is also possible to use the sync operation to ensure only the given IDs will be contained withing the relation.

mutation {
  createPost(input: { title: "My new Post", authors: { sync: [123] } }) {
    id
    authors {
      name
    }
  }
}

Updates on BelongsToMany relations may expose up to 6 nested operations.

type Mutation {
  updatePost(input: UpdatePostInput! @spread): Post @create
}

input UpdatePostInput {
  id: ID!
  title: String
  authors: UpdateAuthorRelation
}

input UpdateAuthorRelation {
  create: [CreateAuthorInput!]
  connect: [ID!]
  update: [UpdateAuthorInput!]
  sync: [ID!]
  delete: [ID!]
  disconnect: [ID!]
}

input CreateAuthorInput {
  name: String!
}

input UpdateAuthorInput {
  id: ID!
  name: String
}

# MorphTo

type Mutation {
  createHour(input: CreateHourInput! @spread): Hour @create
}

input CreateHourInput {
  hourable_type: String!
  hourable_id: Int!
  from: String
  to: String
  weekday: Int
}

type Hour {
  id: ID
  weekday: Int
  hourable: Task
}

type Task {
  id: ID
  name: String
  hour: Hour
}
mutation {
  createHour(input: {
    hourable_type: "App\\\Task"
    hourable_id: 1
    weekday: 2
  }) {
    id
    weekday
    hourable {
      id
      name
    }
  }
}

# Morph To Many

A morph to many relation allows you to create new related models as well as attaching existing ones.

type Mutation {
  createTask(input: CreateTaskInput!): Task @create(flatten: true)
}

input CreateTaskInput {
  name: String!
  tags: CreateTagRelation
}

input CreateTagRelation {
  create: [CreateTagInput!]
  sync: [ID!]
  connect: [ID!]
}

input CreateTagInput {
  name: String!
}

type Task {
  id: ID!
  name: String!
  tags: [Tag!]!
}

type Tag {
  id: ID!
  name: String!
}

In this example, the tag with id 1 already exists in the database. The query connects this tag to the task using the MorphToMany relationship.

mutation {
  createTask(input: { name: "Loundry", tags: { connect: [1] } }) {
    tags {
      id
      name
    }
  }
}

You can either use connect or sync during creation.

When you want to create a new tag while creating the task, you need use the create operation to provide an array of CreateTagInput:

mutation {
  createTask(input: { name: "Loundry", tags: { create: [{ name: "home" }] } }) {
    tags {
      id
      name
    }
  }
}