--- id: tutorial-todo-gql-mutation-input title: Mutation Inputs sidebar_label: Mutation Inputs --- In this section, we continue the [GraphQL example](tutorial-todo-gql.md) by explaining how to extend the Ent code generator using Go templates and generate [input type](https://graphql.org/graphql-js/mutations-and-input-types/) objects for our GraphQL mutations that can be applied directly on Ent mutations. #### Clone the code (optional) The code for this tutorial is available under [github.com/a8m/ent-graphql-example](https://github.com/a8m/ent-graphql-example), and tagged (using Git) in each step. If you want to skip the basic setup and start with the initial version of the GraphQL server, you can clone the repository and run the program as follows: ```console git clone git@github.com:a8m/ent-graphql-example.git cd ent-graphql-example go run ./cmd/todo/ ``` ## Go Templates The Ent framework accepts external templates that can extend or override the default generated functionality of its code generator. In the template below, we generate 2 input types (`CreateTodoInput` and `UpdateTodoInput`) for the GraphQL mutations, and add additional methods on the different builders to accept these objects as an input type. ```gotemplate title="ent/template/mutation_input.tmpl" {{ range $n := $.Nodes }} {{ $input := print "Create" $n.Name "Input" }} // {{ $input }} represents a mutation input for creating {{ plural $n.Name | lower }}. type {{ $input }} struct { {{- range $f := $n.Fields }} {{- if not $f.IsEdgeField }} {{ $f.StructField }} {{ if and (or $f.Optional $f.Default) (not $f.Type.RType.IsPtr) }}*{{ end }}{{ $f.Type }} {{- end }} {{- end }} {{- range $e := $n.Edges }} {{- if $e.Unique }} {{- $structField := print (pascal $e.Name) "ID" }} {{ $structField }} {{ if $e.Optional }}*{{ end }}{{ $e.Type.ID.Type }} {{- else }} {{- $structField := print (singular $e.Name | pascal) "IDs" }} {{ $structField }} []{{ $e.Type.ID.Type }} {{- end }} {{- end }} } {{/* Additional methods go here. */}} {{ $input = print "Update" $n.Name "Input" }} // {{ $input }} represents a mutation input for updating {{ plural $n.Name | lower }}. type {{ $input }} struct { {{- range $f := $n.MutableFields }} {{- if not $f.IsEdgeField }} {{ $f.StructField }} {{ if not $f.Type.RType.IsPtr }}*{{ end }}{{ $f.Type }} {{- if $f.Optional }} {{ print "Clear" $f.StructField }} bool {{- end }} {{- end }} {{- end }} {{- range $e := $n.Edges }} {{- if $e.Unique }} {{- $structField := print (pascal $e.Name) "ID" }} {{ $structField }} *{{ $e.Type.ID.Type }} {{ $e.MutationClear }} bool {{- else }} {{ $e.MutationAdd }} []{{ $e.Type.ID.Type }} {{ $e.MutationRemove }} []{{ $e.Type.ID.Type }} {{- end }} {{- end }} } {{/* Additional methods go here. */}} {{ end }} ``` The full version of this template exists in the [github.com/a8m/ent-graphql-example/ent/template](https://github.com/a8m/ent-graphql-example/blob/master/ent/template/mutation_input.tmpl). :::info If you have no experience with Go templates or if you have not used it before with the Ent code generator, go to the [template documentation](templates.md) to learn more about it. The full documentation for the template API (Go types and functions) is available in the [pkg.go.dev/entgo.io/ent/entc/gen](https://pkg.go.dev/entgo.io/ent/entc/gen). ::: Now, we tell the Ent code generator to execute this template by passing it as an argument in the `ent/entc.go` file: ```go {8} title="ent/entc.go" func main() { ex, err := entgql.NewExtension() if err != nil { log.Fatalf("creating entgql extension: %v", err) } opts := []entc.Option{ entc.Extensions(ex), entc.TemplateDir("./template"), } if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil { log.Fatalf("running ent codegen: %v", err) } } ``` After adding the template file to the `ent/template/` directory and changing the `entc.go` configuration, we're ready to execute the code generation as follows: ``` go generate ./... ``` You may have noticed that Ent generated a new file `ent/mutation_input.go` with the following content: ```go title="ent/template/mutation_input.go" // Code generated by ent, DO NOT EDIT. package ent import ( "time" "todo/ent/todo" ) // CreateTodoInput represents a mutation input for creating todos. type CreateTodoInput struct { Text string CreatedAt time.Time Status todo.Status Priority int Children []int Parent *int } // Mutate applies the CreateTodoInput on the TodoCreate builder. func (i *CreateTodoInput) Mutate(m *TodoCreate) { // ... } // UpdateTodoInput represents a mutation input for updating todos. type UpdateTodoInput struct { Text *string Status *todo.Status Priority *int AddChildIDs []int RemoveChildIDs []int Parent *int ClearParent bool } // Mutate applies the UpdateTodoInput on the TodoMutation. func (i *UpdateTodoInput) Mutate(m *TodoMutation) { // ... } ``` ## Input Types In GraphQL Schema The new generated Go types are the GraphQL mutation types. Let's define them manually in the GraphQL schema and `gqlgen` will map them automatically. ```graphql title="todo.graphql" # Define an input type for the mutation below. # https://graphql.org/learn/schema/#input-types # # Note that, this type is mapped to the generated # input type in mutation_input.go. input CreateTodoInput { status: Status! = IN_PROGRESS priority: Int text: String! text: String parent: ID children: [ID!] } # Define an input type for the mutation below. # https://graphql.org/learn/schema/#input-types # # Note that, this type is mapped to the generated # input type in mutation_input.go. input UpdateTodoInput { status: Status priority: Int text: String parent: ID clearParent: Boolean addChildIDs: [ID!] removeChildIDs: [ID!] } # Define a mutation for creating todos. # https://graphql.org/learn/queries/#mutations type Mutation { createTodo(input: CreateTodoInput!): Todo! updateTodo(id: ID!, input: UpdateTodoInput!): Todo! updateTodos(ids: [ID!]!, input: UpdateTodoInput!): [Todo!]! } ``` We're ready now to run the `gqlgen` code generator and generate resolvers for the new mutations. ``` go generate ./... ``` The result is as follows: ```go title="todo.resolvers.go" func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) { panic(fmt.Errorf("not implemented")) } func (r *mutationResolver) UpdateTodo(ctx context.Context, id int, input ent.UpdateTodoInput) (*ent.Todo, error) { panic(fmt.Errorf("not implemented")) } func (r *mutationResolver) UpdateTodos(ctx context.Context, ids []int, input ent.UpdateTodoInput) ([]*ent.Todo, error) { panic(fmt.Errorf("not implemented")) } ``` ## Apply Input Types on `ent.Client` The `Set` calls in the `CreateTodo` resolver are replaced with one call named `SetInput`: ```diff title="todo.resolvers.go" func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) { return ent.FromContext(ctx).Todo. Create(). - SetText(todo.Text). - SetStatus(todo.Status). - SetNillablePriority(todo.Priority). // Set the "priority" field if provided. - SetNillableParentID(todo.Parent). // Set the "parent_id" field if provided. + SetInput(input) Save(ctx) } ``` The rest of the resolvers (`UpdateTodo` and `UpdateTodos`) will be implemented as follows: ```diff title="todo.resolvers.go" func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) { return ent.FromContext(ctx).Todo.Create().SetInput(input).Save(ctx) } func (r *mutationResolver) UpdateTodo(ctx context.Context, id int, input ent.UpdateTodoInput) (*ent.Todo, error) { - panic(fmt.Errorf("not implemented")) + return ent.FromContext(ctx).Todo.UpdateOneID(id).SetInput(input).Save(ctx) } func (r *mutationResolver) UpdateTodos(ctx context.Context, ids []int, input ent.UpdateTodoInput) ([]*ent.Todo, error) { - panic(fmt.Errorf("not implemented")) + client := ent.FromContext(ctx) + if err := client.Todo.Update().Where(todo.IDIn(ids...)).SetInput(input).Exec(ctx); err != nil { + return nil, err + } + return client.Todo.Query().Where(todo.IDIn(ids...)).All(ctx) } ``` Hurray! We're now ready to test our GraphQL resolvers. ## Test the `CreateTodo` Resolver Let's start with creating 2 todo items by executing this query with the variables below: ```graphql mutation CreateTodo($input: CreateTodoInput!) { createTodo(input: $input) { id text createdAt priority parent { id } } } ``` #### 1st query variables ```json { "input": { "text": "Create GraphQL Example", "status": "IN_PROGRESS", "priority": 2 } } ``` #### Output ```json { "data": { "createTodo": { "id": "1", "text": "Create GraphQL Example", "createdAt": "2021-04-19T10:49:52+03:00", "priority": 2, "parent": null } } } ``` #### 2nd query variables ```json { "input": { "text": "Create Tracing Example", "status": "IN_PROGRESS", "priority": 2 } } ``` #### Output ```json { "data": { "createTodo": { "id": "2", "text": "Create Tracing Example", "createdAt": "2021-04-19T10:50:01+03:00", "priority": 2, "parent": null } } } ``` ## Test the `UpdateTodos` Resolver We continue the example by updating the `priority` of the 2 todo items to `1`. ```graphql mutation UpdateTodos($ids: [ID!]!, $input: UpdateTodoInput!) { updateTodos(ids: $ids, input: $input) { id text createdAt priority parent { id } } } ``` #### Query variables ```json { "ids": ["1", "2"], "input": { "priority": 1 } } ``` #### Output ```json { "data": { "updateTodos": [ { "id": "1", "text": "Create GraphQL Example", "createdAt": "2021-04-19T10:49:52+03:00", "priority": 1, "parent": null }, { "id": "2", "text": "Create Tracing Example", "createdAt": "2021-04-19T10:50:01+03:00", "priority": 1, "parent": null } ] } } ``` ## Test the `UpdateTodo` Resolver The only thing left is to test the `UpdateTodo` resolver. Let's use it to update the `parent` of the 2nd todo item to `1`. ```graphql mutation UpdateTodo($id: ID!, $input: UpdateTodoInput!) { updateTodo(id: $id, input: $input) { id text createdAt priority parent { id text } } } ``` #### Query variables ```json { "id": "2", "input": { "parent": 1 } } ``` #### Output ```json { "data": { "updateTodo": { "id": "2", "text": "Create Tracing Example", "createdAt": "2021-04-19T10:50:01+03:00", "priority": 1, "parent": { "id": "1", "text": "Create GraphQL Example" } } } } ```