mirror of
https://github.com/ent/ent.git
synced 2026-04-28 13:40:56 +03:00
* doc/tutorial: add page about filter inputs Also, fixed some issues in website configuration * Update doc/md/tutorial-todo-gql-filter-input.md Co-authored-by: Rotem Tamir <rotemtamir@gmail.com> Co-authored-by: Rotem Tamir <rotemtamir@gmail.com>
434 lines
11 KiB
Markdown
Executable File
434 lines
11 KiB
Markdown
Executable File
---
|
|
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
|
|
{{ 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 }}
|
|
{{ $f.StructField }} {{ if and (or $f.Optional $f.Default) (not $f.Type.RType.IsPtr) }}*{{ end }}{{ $f.Type }}
|
|
{{- end }}
|
|
{{- range $e := $n.Edges }}
|
|
{{- if $e.Unique }}
|
|
{{ $e.StructField }} {{ if $e.Optional }}*{{ end }}{{ $e.Type.ID.Type }}
|
|
{{- else }}
|
|
{{ $e.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 }}
|
|
{{ $f.StructField }} {{ if not $f.Type.RType.IsPtr }}*{{ end }}{{ $f.Type }}
|
|
{{- if $f.Optional }}
|
|
{{ print "Clear" $f.StructField }} bool
|
|
{{- end }}
|
|
{{- end }}
|
|
{{- range $e := $n.Edges }}
|
|
{{- if $e.Unique }}
|
|
{{ $e.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
|
|
// Code generated by entc, 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
|
|
# 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
|
|
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<F>` calls in the `CreateTodo` resolver are replaced with one call named `SetInput`:
|
|
|
|
```diff
|
|
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
|
|
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"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|