mirror of
https://github.com/ent/ent.git
synced 2026-04-28 05:30:56 +03:00
doc/md: add tutorial on how to generate mutation input types (#1476)
* doc/md: add tutorial on how to generate mutation input types * address codereview comments
This commit is contained in:
429
doc/md/tutorial-todo-gql-mutation-input.md
Executable file
429
doc/md/tutorial-todo-gql-mutation-input.md
Executable file
@@ -0,0 +1,429 @@
|
||||
---
|
||||
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 }}
|
||||
{{ "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:
|
||||
|
||||
```diff
|
||||
func main() {
|
||||
err := entc.Generate("./schema", &gen.Config{
|
||||
Templates: entgql.AllTemplates,
|
||||
- })
|
||||
+ }, entc.TemplateDir("./template"))
|
||||
if 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -49,3 +49,9 @@ func (mutationResolver) CreateTodo(ctx context.Context, todo TodoInput) (*ent.To
|
||||
Save(ctx)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Great! With a few lines of code, our application now supports automatic transactional mutations. Please continue to the
|
||||
next section where we explain how to extend the Ent code generator and generate [GraphQL input types](https://graphql.org/graphql-js/mutations-and-input-types/)
|
||||
for our GraphQL mutations.
|
||||
@@ -81,6 +81,7 @@ module.exports = {
|
||||
'tutorial-todo-gql-paginate',
|
||||
'tutorial-todo-gql-field-collection',
|
||||
'tutorial-todo-gql-tx-mutation',
|
||||
'tutorial-todo-gql-mutation-input',
|
||||
],
|
||||
collapsed: false,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user