Mutation Inputs

Clone the code (optional)

The code for this tutorial is available under 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:

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.

  1. {{ range $n := $.Nodes }}
  2. {{ $input := print "Create" $n.Name "Input" }}
  3. // {{ $input }} represents a mutation input for creating {{ plural $n.Name | lower }}.
  4. type {{ $input }} struct {
  5. {{- range $f := $n.Fields }}
  6. {{- if not $f.IsEdgeField }}
  7. {{ $f.StructField }} {{ if and (or $f.Optional $f.Default) (not $f.Type.RType.IsPtr) }}*{{ end }}{{ $f.Type }}
  8. {{- end }}
  9. {{- end }}
  10. {{- range $e := $n.Edges }}
  11. {{- if $e.Unique }}
  12. {{- $structField := print (pascal $e.Name) "ID" }}
  13. {{ $structField }} {{ if $e.Optional }}*{{ end }}{{ $e.Type.ID.Type }}
  14. {{- else }}
  15. {{- $structField := print (singular $e.Name | pascal) "IDs" }}
  16. {{ $structField }} []{{ $e.Type.ID.Type }}
  17. {{- end }}
  18. {{- end }}
  19. }
  20. {{/* Additional methods go here. */}}
  21. {{ $input = print "Update" $n.Name "Input" }}
  22. // {{ $input }} represents a mutation input for updating {{ plural $n.Name | lower }}.
  23. type {{ $input }} struct {
  24. {{- range $f := $n.MutableFields }}
  25. {{- if not $f.IsEdgeField }}
  26. {{ $f.StructField }} {{ if not $f.Type.RType.IsPtr }}*{{ end }}{{ $f.Type }}
  27. {{- if $f.Optional }}
  28. {{ print "Clear" $f.StructField }} bool
  29. {{- end }}
  30. {{- end }}
  31. {{- end }}
  32. {{- range $e := $n.Edges }}
  33. {{- if $e.Unique }}
  34. {{- $structField := print (pascal $e.Name) "ID" }}
  35. {{ $structField }} *{{ $e.Type.ID.Type }}
  36. {{ $e.MutationClear }} bool
  37. {{- else }}
  38. {{ $e.MutationAdd }} []{{ $e.Type.ID.Type }}
  39. {{ $e.MutationRemove }} []{{ $e.Type.ID.Type }}
  40. {{- end }}
  41. {{- end }}
  42. }
  43. {{/* Additional methods go here. */}}
  44. {{ end }}

The full version of this template exists in the .

info" class="reference-link">Mutation Inputs - 图2info

If you have no experience with Go templates or if you have not used it before with the Ent code generator, go to the 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.

ent/entc.go

  1. func main() {
  2. ex, err := entgql.NewExtension()
  3. if err != nil {
  4. log.Fatalf("creating entgql extension: %v", err)
  5. }
  6. opts := []entc.Option{
  7. entc.Extensions(ex),
  8. entc.TemplateDir("./template"),
  9. }
  10. if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
  11. log.Fatalf("running ent codegen: %v", err)
  12. }
  13. }

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:

  1. go generate ./...

You may have noticed that Ent generated a new file ent/mutation_input.go with the following content:

  1. // Code generated by entc, DO NOT EDIT.
  2. package ent
  3. import (
  4. "time"
  5. "todo/ent/todo"
  6. )
  7. // CreateTodoInput represents a mutation input for creating todos.
  8. type CreateTodoInput struct {
  9. Text string
  10. CreatedAt time.Time
  11. Status todo.Status
  12. Priority int
  13. Children []int
  14. Parent *int
  15. // Mutate applies the CreateTodoInput on the TodoCreate builder.
  16. func (i *CreateTodoInput) Mutate(m *TodoCreate) {
  17. // ...
  18. }
  19. // UpdateTodoInput represents a mutation input for updating todos.
  20. type UpdateTodoInput struct {
  21. Text *string
  22. Priority *int
  23. AddChildIDs []int
  24. RemoveChildIDs []int
  25. Parent *int
  26. ClearParent bool
  27. }
  28. // Mutate applies the UpdateTodoInput on the TodoMutation.
  29. func (i *UpdateTodoInput) Mutate(m *TodoMutation) {
  30. // ...
  31. }

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.

  1. # Define an input type for the mutation below.
  2. # https://graphql.org/learn/schema/#input-types
  3. #
  4. # Note that, this type is mapped to the generated
  5. # input type in mutation_input.go.
  6. input CreateTodoInput {
  7. status: Status! = IN_PROGRESS
  8. priority: Int
  9. text: String!
  10. text: String
  11. parent: ID
  12. children: [ID!]
  13. }
  14. # Define an input type for the mutation below.
  15. # https://graphql.org/learn/schema/#input-types
  16. #
  17. # Note that, this type is mapped to the generated
  18. # input type in mutation_input.go.
  19. input UpdateTodoInput {
  20. status: Status
  21. priority: Int
  22. text: String
  23. parent: ID
  24. clearParent: Boolean
  25. addChildIDs: [ID!]
  26. removeChildIDs: [ID!]
  27. }
  28. # Define a mutation for creating todos.
  29. # https://graphql.org/learn/queries/#mutations
  30. type Mutation {
  31. createTodo(input: CreateTodoInput!): Todo!
  32. updateTodo(id: ID!, input: UpdateTodoInput!): Todo!
  33. updateTodos(ids: [ID!]!, input: UpdateTodoInput!): [Todo!]!
  34. }

We’re ready now to run the gqlgen code generator and generate resolvers for the new mutations.

  1. go generate ./...

The Set<F> calls in the CreateTodo resolver are replaced with one call named SetInput:

  1. func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) {
  2. return ent.FromContext(ctx).Todo.
  3. Create().
  4. - SetText(todo.Text).
  5. - SetStatus(todo.Status).
  6. - SetNillablePriority(todo.Priority). // Set the "priority" field if provided.
  7. - SetNillableParentID(todo.Parent). // Set the "parent_id" field if provided.
  8. + SetInput(input)
  9. Save(ctx)
  10. }

The rest of the resolvers (UpdateTodo and UpdateTodos) will be implemented as follows:

  1. func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) {
  2. return ent.FromContext(ctx).Todo.Create().SetInput(input).Save(ctx)
  3. }
  4. func (r *mutationResolver) UpdateTodo(ctx context.Context, id int, input ent.UpdateTodoInput) (*ent.Todo, error) {
  5. - panic(fmt.Errorf("not implemented"))
  6. + return ent.FromContext(ctx).Todo.UpdateOneID(id).SetInput(input).Save(ctx)
  7. }
  8. func (r *mutationResolver) UpdateTodos(ctx context.Context, ids []int, input ent.UpdateTodoInput) ([]*ent.Todo, error) {
  9. - panic(fmt.Errorf("not implemented"))
  10. + client := ent.FromContext(ctx)
  11. + if err := client.Todo.Update().Where(todo.IDIn(ids...)).SetInput(input).Exec(ctx); err != nil {
  12. + return nil, err
  13. + }
  14. + return client.Todo.Query().Where(todo.IDIn(ids...)).All(ctx)
  15. }

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:

  1. mutation CreateTodo($input: CreateTodoInput!) {
  2. createTodo(input: $input) {
  3. id
  4. text
  5. createdAt
  6. priority
  7. parent {
  8. id
  9. }
  10. }

1st query variables

  1. {
  2. "input": {
  3. "status": "IN_PROGRESS",
  4. "priority": 2
  5. }
  6. }

Output

  1. {
  2. "data": {
  3. "createTodo": {
  4. "id": "1",
  5. "text": "Create GraphQL Example",
  6. "createdAt": "2021-04-19T10:49:52+03:00",
  7. "priority": 2,
  8. "parent": null
  9. }
  10. }
  11. }

2nd query variables

  1. {
  2. "input": {
  3. "text": "Create Tracing Example",
  4. "status": "IN_PROGRESS",
  5. "priority": 2
  6. }
  7. }

Output

We continue the example by updating the priority of the 2 todo items to 1.

  1. mutation UpdateTodos($ids: [ID!]!, $input: UpdateTodoInput!) {
  2. updateTodos(ids: $ids, input: $input) {
  3. id
  4. text
  5. createdAt
  6. priority
  7. parent {
  8. id
  9. }
  10. }
  11. }

Query variables

  1. {
  2. "ids": ["1", "2"],
  3. "input": {
  4. "priority": 1
  5. }
  6. }

Output

  1. {
  2. "data": {
  3. "updateTodos": [
  4. {
  5. "id": "1",
  6. "text": "Create GraphQL Example",
  7. "createdAt": "2021-04-19T10:49:52+03:00",
  8. "priority": 1,
  9. "parent": null
  10. },
  11. {
  12. "id": "2",
  13. "text": "Create Tracing Example",
  14. "createdAt": "2021-04-19T10:50:01+03:00",
  15. "priority": 1,
  16. "parent": null
  17. }
  18. ]
  19. }
  20. }

Test the UpdateTodo Resolver

  1. mutation UpdateTodo($id: ID!, $input: UpdateTodoInput!) {
  2. updateTodo(id: $id, input: $input) {
  3. id
  4. text
  5. createdAt
  6. priority
  7. parent {
  8. id
  9. text
  10. }
  11. }
  12. }

Query variables

  1. {
  2. "id": "2",
  3. "input": {
  4. "parent": 1
  5. }
  6. }

Output

  1. {
  2. "data": {
  3. "updateTodo": {
  4. "id": "2",
  5. "text": "Create Tracing Example",
  6. "createdAt": "2021-04-19T10:50:01+03:00",
  7. "priority": 1,
  8. "parent": {
  9. "id": "1",
  10. "text": "Create GraphQL Example"
  11. }
  12. }
  13. }