File: graphql.md

package info (click to toggle)
golang-github-facebook-ent 0.5.4-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 14,284 kB
  • sloc: javascript: 349; makefile: 8
file content (357 lines) | stat: -rw-r--r-- 10,542 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
---
id: graphql
title: GraphQL Integration
---

The `ent` framework provides an integration with GraphQL through the [99designs/gqlgen](https://github.com/99designs/gqlgen)
library using the [external templates](templates.md) option (i.e. it can be extended to support other libraries).

## Quick Introduction

In order to enable the [`entgql`](https://github.com/facebookincubator/ent-contrib/tree/master/entgql) extension to your
project, you need to use the `entc` (ent codegen) package as described [here](code-gen.md#use-entc-as-a-package).
Follow these 3 steps to enable it to your project:

1\. Create a new Go file named `ent/entc.go`, and paste the following content:

```go
// +build ignore

package main

import (
	"log"

	"github.com/facebook/ent/entc"
	"github.com/facebook/ent/entc/gen"
	"github.com/facebookincubator/ent-contrib/entgql"
)

func main() {
	err := entc.Generate("./schema", &gen.Config{
		Templates: entgql.AllTemplates,
	})
	if err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}
```

2\. Edit the `ent/generate.go` file to execute the `ent/entc.go` file:

```go
package ent

//go:generate go run entc.go
```

Note that `ent/entc.go` is ignored using a build tag, and it's executed by the `go generate` command
through the `generate.go` file. The full example can be found in the [ent-contrib repository](https://github.com/facebookincubator/ent-contrib/blob/master/entgql/internal/todo).


3\. Run codegen for your ent project:

```console
go generate ./...
```

After running codegen, the following add-ons will be added to your project.

## Node API

A new file named `ent/node.go` was created that implements the [Relay Node interface](https://relay.dev/docs/en/graphql-server-specification.html#object-identification).

In order to use the new generated `ent.Noder` interface in the [GraphQL resolver](https://gqlgen.com/reference/resolvers/),
add the `Node` method to the query resolver, and look at the [configuration](#gql-configuration) section to understand
how to use it.

If you are using the [Universal IDs](migrate.md#universal-ids) option in the schema migration, the NodeType is derived
from the id value and can be used as follows:

```go
func (r *queryResolver) Node(ctx context.Context, id int) (ent.Noder, error) {
	return r.client.Noder(ctx, id)
}
```

However, if you use a custom format for the global unique identifiers, you can control the NodeType as follows:

```go
func (r *queryResolver) Node(ctx context.Context, guid string) (ent.Noder, error) {
	typ, id := parseGUID(guid)
	return r.client.Noder(ctx, id, ent.WithNodeType(typ))
}
```


## GQL Configuration

Here's a configuration example for a todo app as exists in [ent-contrib/entgql/todo](https://github.com/facebookincubator/ent-contrib/tree/master/entgql/internal/todo).

```yaml
schema:
  - todo.graphql

resolver:
  # Tell gqlgen to generate resolvers next to the schema file.
  layout: follow-schema
  dir: .

# gqlgen will search for any type names in the schema in the generated
# ent package. If they match it will use them, otherwise it will new ones.
autobind:
  - github.com/facebookincubator/ent-contrib/entgql/internal/todo/ent

models:
  ID:
    model:
      - github.com/99designs/gqlgen/graphql.IntID
  Node:
    model:
      # ent.Noder is the new interface generated by the Node template.
      - github.com/facebookincubator/ent-contrib/entgql/internal/todo/ent.Noder
```

## Pagination

The pagination template adds a pagination support according to the _Relay Cursor Connections Spec_. More info
about the Relay Spec can be found in its [website](https://relay.dev/graphql/connections.htm).

## Connection Ordering

The ordering option allows us to apply an ordering on the edges returned from a connection.

### Usage Notes

- The generated types will be `autobind`ed to GraphQL types if a naming convention is preserved (see example below).
- Ordering can only be defined on ent fields (no edges).
- Ordering fields should normally be [indexed](schema-indexes.md) to avoid full table DB scan.
- Pagination queries can be sorted by a single field (no order by ... then by ... semantics).

### Example

Let's go over the steps needed in order to add ordering to an existing GraphQL type.
The code example is based on a todo-app that can be found in [ent-contrib/entql/todo](https://github.com/facebookincubator/ent-contrib/tree/master/entgql/internal/todo).

### Defining order fields in ent/schema

Ordering can be defined on any comparable field of ent by annotating it with `entgql.Annotation`.
Note that the given `OrderField` name must match its enum value in graphql schema.
```go
func (Todo) Fields() []ent.Field {
    return []ent.Field{
	    field.Time("created_at").
			Default(time.Now).
			Immutable().
			Annotations(
				entgql.OrderField("CREATED_AT"),
			),
		field.Enum("status").
			NamedValues(
				"InProgress", "IN_PROGRESS",
				"Completed", "COMPLETED",
			).
			Annotations(
				entgql.OrderField("STATUS"),
			),
		field.Int("priority").
			Default(0).
			Annotations(
				entgql.OrderField("PRIORITY"),
			),
		field.Text("text").
			NotEmpty().
			Annotations(
				entgql.OrderField("TEXT"),
			),
    }
}
```
That's all the schema changes required, make sure to run `go generate` to apply them.

### Define ordering types in GraphQL schema

Next we need to define the ordering types in graphql schema:
```graphql
enum OrderDirection {
  ASC
  DESC
}

enum TodoOrderField {
  CREATED_AT
  PRIORITY
  STATUS
  TEXT
}

input TodoOrder {
  direction: OrderDirection!
  field: TodoOrderField
}
```
Note that the naming must take the form of `<T>OrderField` / `<T>Order` for `autobind`ing to the generated ent types.
Alternatively [@goModel](https://gqlgen.com/config/#inline-config-with-directives) directive can be used for manual type binding.

### Adding orderBy argument to the pagination query
```graphql
type Query {
  todos(
    after: Cursor
    first: Int
    before: Cursor
    last: Int
    orderBy: TodoOrder
  ): TodoConnection
}
```
That's all for the GraphQL schema changes, let's run `gqlgen` code generation.

### Update the underlying resolver

Head over to the Todo resolver and update it to pass `orderBy` argument to `.Paginate()` call:

```go
func (r *queryResolver) Todos(ctx context.Context, after *ent.Cursor, first *int, before *ent.Cursor, last *int, orderBy *ent.TodoOrder) (*ent.TodoConnection, error) {
	return r.client.Todo.Query().
		Paginate(ctx, after, first, before, last,
			ent.WithTodoOrder(orderBy),
		)
}
```

### Use in GraphQL

```text
query {
    todos(first: 3, orderBy: {direction: DESC, field: NAME}) {
        edges {
            node {
                name
            }
        }
    }
}
```

## Fields Collection

The collection template adds support for automatic [GraphQL fields collection](https://spec.graphql.org/June2018/#sec-Field-Collection)
for ent-edges using eager-loading. That means, if a query asks for nodes and their edges, entgql will add automatically [`With<E>`](eager-load.md#api)
steps to the root query, and as a result, the client will execute constant number of queries to the database - and it works recursively.

For example, given this GraphQL query:

```graphql
query {
  users(first: 100) {
    edges {
      node {
        photos {
          link
        }
        posts {
          content
          comments {
            content
          }
        }
      }
    }
  }
}
```

The client will execute 1 query for getting the users, 1 for getting the photos, and another 2 for getting the posts,
and their comments (4 in total). This logic works both for root queries/resolvers and for the node(s) API.

### Schema configuration

In order to configure this option to specific edges, use the `entgql.Annotation` as follows:

```go
func (Todo) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("children", Todo.Type).
            Annotations(entgql.Bind()).
            From("parent").
            // Bind implies the edge name in graphql schema is
            // equivalent to the name used in ent schema.
            Annotations(entgql.Bind()).
            Unique(),
        edge.From("owner", User.Type).
            Ref("tasks").
            // Map edge names as defined in graphql schema.
            Annotations(entgql.MapsTo("taskOwner")),
    }
}
```

### Usage and Configuration

The GraphQL extension generates also edge-resolvers for the nodes under the `edge.go` file as follows:
```go
func (t *Todo) Children(ctx context.Context) ([]*Todo, error) {
	result, err := t.Edges.ChildrenOrErr()
	if IsNotLoaded(err) {
		result, err = t.QueryChildren().All(ctx)
	}
	return result, err
}
```

However, if you need to explicitly write these resolvers by hand, you can add the
[`forceResolver`](https://gqlgen.com/master/config#inline-config-with-directives) option to your GraphQL schema:

```graphql
type Todo implements Node {
  id: ID!
  children: [Todo]! @goField(forceResolver: true)
}
```

Then, you can implement it on your type resolver.

```go
func (r *todoResolver) Children(ctx context.Context, obj *ent.Todo) ([]*ent.Todo, error) {
	// Do something here.
	return obj.Edges.ChildrenOrErr()
}
```

## Enum Implementation

The enum template implements the MarshalGQL/UnmarshalGQL methods for enums generated by ent.

## Transactional Mutations

The `entgql.Transactioner` handler executes each GraphQL mutation in a transaction. The injected client for the resolver
is a [transactional `ent.Client`](transactions.md#transactional-client).
Hence, code that uses `ent.Client` won't need to be changed. In order to use it, follow these steps:


1\. In the GraphQL server initialization, use the `entgql.Transactioner` handler as follows:

```go
srv := handler.NewDefaultServer(todo.NewSchema(client))
srv.Use(entgql.Transactioner{TxOpener: client})
```

2\. And then, in the GraphQL mutations, use the client from context as follows:
```go
func (mutationResolver) CreateTodo(ctx context.Context, todo TodoInput) (*ent.Todo, error) {
	client := ent.FromContext(ctx)
	return client.Todo.
		Create().
		SetStatus(todo.Status).
		SetNillablePriority(todo.Priority).
		SetText(todo.Text).
		SetNillableParentID(todo.Parent).
		Save(ctx)
}
```

---

Please note that this documentation is under development. All code parts reside in [ent-contrib/entgql](https://github.com/facebookincubator/ent-contrib/tree/master/entgql),
and an example of a todo-app can be found in [ent-contrib/entql/todo](https://github.com/facebookincubator/ent-contrib/tree/master/entgql/internal/todo).