Filter Inputs

In this section, we continue the GraphQL example by explaining how to generate type-safe GraphQL filters (i.e. Where predicates) from our ent/schema, and allow users to seamlessly map GraphQL queries to Ent queries. For example, the following GraphQL query, maps to the Ent query below:

GraphQL

  1. {
  2. hasParent: true,
  3. hasChildrenWith: {
  4. status: IN_PROGRESS,
  5. }
  6. }

Ent

  1. client.Todo.
  2. Query().
  3. Where(
  4. todo.HasParent(),
  5. todo.HasChildrenWith(
  6. todo.StatusEQ(todo.StatusInProgress),
  7. ),
  8. ).
  9. All(ctx)

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/

Configure Ent

Go to your ent/entc.go file, and add the 3 highlighted lines (extension options):

ent/entc.go

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

The WithWhereFilters option enables the filter generation, the WithConfigPath configures the path to the gqlgen config file, which allows the extension to more accurately map GraphQL to Ent types. The last option WithSchemaPath, configures a path to a new, or an existing GraphQL schema to write the generated filters to.

After changing the entc.go configuration, we’re ready to execute the code generation as follows:

  1. go generate ./ent/...

Observe that Ent has generated <T>WhereInput for each type in your schema in a file named ent/where_input.go. Ent also generates a GraphQL schema as well (ent.graphql), so you don’t need to autobind them to gqlgen manually. For example:

ent/where_input.go

  1. // TodoWhereInput represents a where input for filtering Todo queries.
  2. type TodoWhereInput struct {
  3. Not *TodoWhereInput `json:"not,omitempty"`
  4. Or []*TodoWhereInput `json:"or,omitempty"`
  5. And []*TodoWhereInput `json:"and,omitempty"`
  6. // "created_at" field predicates.
  7. CreatedAt *time.Time `json:"createdAt,omitempty"`
  8. CreatedAtNEQ *time.Time `json:"createdAtNEQ,omitempty"`
  9. CreatedAtIn []time.Time `json:"createdAtIn,omitempty"`
  10. CreatedAtNotIn []time.Time `json:"createdAtNotIn,omitempty"`
  11. CreatedAtGT *time.Time `json:"createdAtGT,omitempty"`
  12. CreatedAtGTE *time.Time `json:"createdAtGTE,omitempty"`
  13. CreatedAtLT *time.Time `json:"createdAtLT,omitempty"`
  14. CreatedAtLTE *time.Time `json:"createdAtLTE,omitempty"`
  15. // "status" field predicates.
  16. Status *todo.Status `json:"status,omitempty"`
  17. StatusNEQ *todo.Status `json:"statusNEQ,omitempty"`
  18. StatusIn []todo.Status `json:"statusIn,omitempty"`
  19. StatusNotIn []todo.Status `json:"statusNotIn,omitempty"`
  20. // .. truncated ..
  21. }

ent.graphql

  1. """
  2. TodoWhereInput is used for filtering Todo objects.
  3. Input was generated by ent.
  4. """
  5. input TodoWhereInput {
  6. not: TodoWhereInput
  7. and: [TodoWhereInput!]
  8. or: [TodoWhereInput!]
  9. """created_at field predicates"""
  10. createdAt: Time
  11. createdAtNEQ: Time
  12. createdAtIn: [Time!]
  13. createdAtNotIn: [Time!]
  14. createdAtGT: Time
  15. createdAtGTE: Time
  16. createdAtLT: Time
  17. createdAtLTE: Time
  18. """status field predicates"""
  19. status: Status
  20. statusNEQ: Status
  21. statusIn: [Status!]
  22. statusNotIn: [Status!]
  23. # .. truncated ..
  24. }
If your project contains more than 1 GraphQL schema (e.g. todo.graphql and ent.graphql), you should configure gqlgen.yml file as follows:" class="reference-link">Filter Inputs - 图2If your project contains more than 1 GraphQL schema (e.g. todo.graphql and ent.graphql), you should configure gqlgen.yml file as follows:
  1. schema:
  2. - todo.graphql
  3. # The ent.graphql schema was generated by Ent.
  4. - ent.graphql

Configure GQL

After running the code generation, we’re ready to complete the integration and expose the filtering capabilities in GraphQL:

1. Edit the GraphQL schema to accept the new filter types:

  1. type Query {
  2. todos(
  3. after: Cursor,
  4. first: Int,
  5. before: Cursor,
  6. last: Int,
  7. orderBy: TodoOrder,
  8. where: TodoWhereInput,
  9. ): TodoConnection
  10. }

2. Use the new filter types in GraphQL resolvers:

  1. func (r *queryResolver) Todos(ctx context.Context, after *ent.Cursor, first *int, before *ent.Cursor, last *int, orderBy *ent.TodoOrder, where *ent.TodoWhereInput) (*ent.TodoConnection, error) {
  2. return r.client.Todo.Query().
  3. Paginate(ctx, after, first, before, last,
  4. ent.WithTodoOrder(orderBy),
  5. ent.WithTodoFilter(where.Filter),
  6. )
  7. }

Execute Queries

As mentioned above, with the new GraphQL filter types, you can express the same Ent filters you use in your Go code.

Conjunction, disjunction and negation

The Not, And and Or operators can be added using the not, and and or fields. For example:

  1. {
  2. or: [
  3. {
  4. status: COMPLETED,
  5. },
  6. {
  7. not: {
  8. hasParent: true,
  9. status: IN_PROGRESS,
  10. }
  11. }
  12. ]
  13. }

When multiple filter fields are provided, Ent implicitly adds the And operator.

  1. {
  2. status: COMPLETED,
  3. textHasPrefix: "GraphQL",
  4. }

The above query will produce the following Ent query:

  1. client.Todo.
  2. Query().
  3. Where(
  4. todo.And(
  5. todo.StatusEQ(todo.StatusCompleted),
  6. todo.TextHasPrefix("GraphQL"),
  7. )
  8. ).
  9. All(ctx)

Edge/Relation filters

Edge (relation) predicates can be expressed in the same Ent syntax:

  1. {
  2. hasParent: true,
  3. hasChildrenWith: {
  4. status: IN_PROGRESS,
  5. }
  6. }

The above query will produce the following Ent query:

  1. client.Todo.
  2. Query().
  3. Where(
  4. todo.HasParent(),
  5. todo.HasChildrenWith(
  6. todo.StatusEQ(todo.StatusInProgress),
  7. ),
  8. ).
  9. All(ctx)

Well done! As you can see, by changing a few lines of code our application now exposes a type-safe GraphQL filters that automatically map to Ent queries. Have questions? Need help with getting started? Feel free to join our Slack channel.