# 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 modelcreate
a new related model and attach itupdate
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
}
}
}