Mutation Inputs

In this section, we continue the GraphQL example by explaining how to extend the Ent code generator using Go templates and generate input type 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, 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:

  1. git clone git@github.com:a8m/ent-graphql-example.git
  2. cd ent-graphql-example
  3. 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.

  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. {{ $f.StructField }} {{ if and (or $f.Optional $f.Default) (not $f.Type.RType.IsPtr) }}*{{ end }}{{ $f.Type }}
  7. {{- end }}
  8. {{- range $e := $n.Edges }}
  9. {{- if $e.Unique }}
  10. {{ $e.StructField }} {{ if $e.Optional }}*{{ end }}{{ $e.Type.ID.Type }}
  11. {{- else }}
  12. {{ $e.StructField }} []{{ $e.Type.ID.Type }}
  13. {{- end }}
  14. {{- end }}
  15. }
  16. {{/* Additional methods go here. */}}
  17. {{ $input = print "Update" $n.Name "Input" }}
  18. // {{ $input }} represents a mutation input for updating {{ plural $n.Name | lower }}.
  19. type {{ $input }} struct {
  20. {{- range $f := $n.MutableFields }}
  21. {{ $f.StructField }} {{ if not $f.Type.RType.IsPtr }}*{{ end }}{{ $f.Type }}
  22. {{- if $f.Optional }}
  23. {{ print "Clear" $f.StructField }} bool
  24. {{- end }}
  25. {{- end }}
  26. {{- range $e := $n.Edges }}
  27. {{- if $e.Unique }}
  28. {{ $e.StructField }} *{{ $e.Type.ID.Type }}
  29. {{ $e.MutationClear }} bool
  30. {{- else }}
  31. {{ $e.MutationAdd }} []{{ $e.Type.ID.Type }}
  32. {{ $e.MutationRemove }} []{{ $e.Type.ID.Type }}
  33. {{- end }}
  34. {{- end }}
  35. }
  36. {{/* Additional methods go here. */}}
  37. {{ end }}

The full version of this template exists in the github.com/a8m/ent-graphql-example/ent/template.

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 template documentation 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.

Now, we tell the Ent code generator to execute this template by passing it as an argument in the ent/entc.go file:

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. }
  16. // Mutate applies the CreateTodoInput on the TodoCreate builder.
  17. func (i *CreateTodoInput) Mutate(m *TodoCreate) {
  18. // ...
  19. }
  20. // UpdateTodoInput represents a mutation input for updating todos.
  21. type UpdateTodoInput struct {
  22. Text *string
  23. Status *todo.Status
  24. Priority *int
  25. AddChildIDs []int
  26. RemoveChildIDs []int
  27. Parent *int
  28. ClearParent bool
  29. }
  30. // Mutate applies the UpdateTodoInput on the TodoMutation.
  31. func (i *UpdateTodoInput) Mutate(m *TodoMutation) {
  32. // ...
  33. }

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 result is as follows:

  1. func (r *mutationResolver) CreateTodo(ctx context.Context, input ent.CreateTodoInput) (*ent.Todo, error) {
  2. panic(fmt.Errorf("not implemented"))
  3. }
  4. func (r *mutationResolver) UpdateTodo(ctx context.Context, id int, input ent.UpdateTodoInput) (*ent.Todo, error) {
  5. panic(fmt.Errorf("not implemented"))
  6. }
  7. func (r *mutationResolver) UpdateTodos(ctx context.Context, ids []int, input ent.UpdateTodoInput) ([]*ent.Todo, error) {
  8. panic(fmt.Errorf("not implemented"))
  9. }

Apply Input Types on ent.Client

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. }
  11. }

1st query variables

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

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

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

Test the UpdateTodos Resolver

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

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.

  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. }
  14. }