View Source Absinthe.Schema behaviour (absinthe v1.7.8)

Build GraphQL Schemas

Custom Schema Manipulation (in progress)

In Absinthe 1.5 and up schemas are built using the same process by which queries are executed. All the macros in this module and in Notation build up an intermediary tree of structs in the %Absinthe.Blueprint{} namespace, which we generally call "Blueprint structs".

At the top you've got a %Blueprint{} struct which holds onto some schema definitions that look a bit like this:

%Blueprint.Schema.SchemaDefinition{
  type_definitions: [
    %Blueprint.Schema.ObjectTypeDefinition{identifier: :query, ...},
    %Blueprint.Schema.ObjectTypeDefinition{identifier: :mutation, ...},
    %Blueprint.Schema.ObjectTypeDefinition{identifier: :user, ...},
    %Blueprint.Schema.EnumTypeDefinition{identifier: :sort_order, ...},
  ]
}

You can see what your schema's blueprint looks like by calling __absinthe_blueprint__() on any schema or type definition module.

defmodule MyAppWeb.Schema do
  use Absinthe.Schema

  query do

  end
end

> MyAppWeb.Schema.__absinthe_blueprint__()
#=> %Absinthe.Blueprint{...}

These blueprints are manipulated by phases, which validate and ultimately construct a schema. This pipeline of phases you can hook into like you do for queries.

defmodule MyAppWeb.Schema do
  use Absinthe.Schema

  @pipeline_modifier MyAppWeb.CustomSchemaPhase

  query do

  end

end

defmodule MyAppWeb.CustomSchemaPhase do
  alias Absinthe.{Phase, Pipeline, Blueprint}

  # Add this module to the pipeline of phases
  # to run on the schema
  def pipeline(pipeline) do
    Pipeline.insert_after(pipeline, Phase.Schema.TypeImports, __MODULE__)
  end

  # Here's the blueprint of the schema, do whatever you want with it.
  def run(blueprint, _) do
    {:ok, blueprint}
  end
end

The blueprint structs are pretty complex, but if you ever want to figure out how to construct something in blueprints you can always just create the thing in the normal AST and then look at the output. Let's see what interfaces look like for example:

defmodule Foo do
  use Absinthe.Schema.Notation

  interface :named do
    field :name, :string
  end
end

Foo.__absinthe_blueprint__() #=> ...

Summary

Callbacks

Used to set some values in the context that it may need in order to run.

Used to hydrate the schema with dynamic attributes.

Used to apply middleware on all or a group of fields based on pattern matching.

Used to define the list of plugins to run before and after resolution.

Functions

Get all concrete types for union, interface, or object

List all directives on a schema

List all implementors of an interface on a schema

Run the introspection query on a schema.

Get all introspection types

Get all types that are referenced by an operation

Converts a schema to an SDL string

List all types on a schema

used_types(schema) deprecated

Get all types that are used by an operation

Types

Callbacks

@callback context(map()) :: map()

Used to set some values in the context that it may need in order to run.

Examples

Setup dataloader:

def context(context) do
  loader =
    Dataloader.new
    |> Dataloader.add_source(Blog, Blog.data())

    Map.put(context, :loader, loader)
end
Link to this callback

hydrate(node, ancestors)

View Source
@callback hydrate(
  node :: Absinthe.Blueprint.Schema.t(),
  ancestors :: [Absinthe.Blueprint.Schema.t()]
) :: Absinthe.Schema.Hydrator.hydration()

Used to hydrate the schema with dynamic attributes.

While this is normally used to add resolvers, etc, to schemas defined using import_sdl/1 and import_sdl/2, it can also be used in schemas defined using other macros.

The function is passed the blueprint definition node as the first argument and its ancestors in a list (with its parent node as the head) as its second argument.

See the Absinthe.Phase.Schema.Hydrate implementation of Absinthe.Schema.Hydrator callbacks to see what hydration values can be returned.

Examples

Add a resolver for a field:

def hydrate(%Absinthe.Blueprint.Schema.FieldDefinition{identifier: :health}, [%Absinthe.Blueprint.Schema.ObjectTypeDefinition{identifier: :query} | _]) do
  {:resolve, &__MODULE__.health/3}
end

# Resolver implementation:
def health(_, _, _), do: {:ok, "alive!"}

Note that the values provided must be macro-escapable; notably, anonymous functions cannot be used.

You can, of course, omit the struct names for brevity:

def hydrate(%{identifier: :health}, [%{identifier: :query} | _]) do
  {:resolve, &__MODULE__.health/3}
end

Add a description to a type:

def hydrate(%Absinthe.Blueprint.Schema.ObjectTypeDefinition{identifier: :user}, _) do
  {:description, "A user"}
end

If you define hydrate/2, don't forget to include a fallback, e.g.:

def hydrate(_node, _ancestors), do: []

Used to apply middleware on all or a group of fields based on pattern matching.

It is passed the existing middleware for a field, the field itself, and the object that the field is a part of.

Examples

Adding a HandleChangesetError middleware only to mutations:

# if it's a field for the mutation object, add this middleware to the end
def middleware(middleware, _field, %{identifier: :mutation}) do
  middleware ++ [MyAppWeb.Middleware.HandleChangesetErrors]
end

# if it's any other object keep things as is
def middleware(middleware, _field, _object), do: middleware
@callback plugins() :: [Absinthe.Plugin.t()]

Used to define the list of plugins to run before and after resolution.

Plugins are modules that implement the Absinthe.Plugin behaviour. These modules have the opportunity to run callbacks before and after the resolution of the entire document, and have access to the resolution accumulator.

Plugins must be specified by the schema, so that Absinthe can make sure they are all given a chance to run prior to resolution.

Functions

Link to this function

apply_modifiers(pipeline, schema, opts \\ [])

View Source
Link to this function

concrete_types(schema, type)

View Source
@spec concrete_types(t(), Absinthe.Type.t()) :: [Absinthe.Type.t()]

Get all concrete types for union, interface, or object

@spec directives(t()) :: [Absinthe.Type.Directive.t()]

List all directives on a schema

Link to this function

implementors(schema, ident)

View Source
@spec implementors(t(), Absinthe.Type.identifier_t() | Absinthe.Type.Interface.t()) ::
  [
    Absinthe.Type.Object.t()
  ]

List all implementors of an interface on a schema

Link to this function

introspect(schema, opts \\ [])

View Source
@spec introspect(schema :: t(), opts :: Absinthe.run_opts()) :: Absinthe.run_result()

Run the introspection query on a schema.

Convenience function.

Link to this function

introspection_types(schema)

View Source
@spec introspection_types(t()) :: [Absinthe.Type.t()]

Get all introspection types

Link to this function

lookup_directive(schema, name)

View Source
Link to this function

lookup_type(schema, type, options \\ [unwrap: true])

View Source
Link to this macro

mutation(raw_attrs \\ [name: "RootMutationType"], list)

View Source (macro)

Defines a root Mutation object

mutation do
  field :create_user, :user do
    arg :name, non_null(:string)
    arg :email, non_null(:string)

    resolve &MyApp.Web.BlogResolvers.create_user/2
  end
end
Link to this macro

query(raw_attrs \\ [name: "RootQueryType"], list)

View Source (macro)

Defines a root Query object

Link to this function

referenced_types(schema)

View Source
@spec referenced_types(t()) :: [Absinthe.Type.t()]

Get all types that are referenced by an operation

Link to this function

replace_default(middleware_list, new_middleware, map, object)

View Source

Replace the default middleware.

Examples

Replace the default for all fields with a string lookup instead of an atom lookup:

def middleware(middleware, field, object) do
  new_middleware = {Absinthe.Middleware.MapGet, to_string(field.identifier)}
  middleware
  |> Absinthe.Schema.replace_default(new_middleware, field, object)
end
Link to this function

schema_declaration(schema)

View Source
Link to this macro

subscription(raw_attrs \\ [name: "RootSubscriptionType"], list)

View Source (macro)

Defines a root Subscription object

Subscriptions in GraphQL let a client submit a document to the server that outlines what data they want to receive in the event of particular updates.

For a full walk through of how to setup your project with subscriptions and Phoenix see the Absinthe.Phoenix project moduledoc.

When you push a mutation, you can have selections on that mutation result to get back data you need, IE

mutation {
  createUser(accountId: 1, name: "bob") {
    id
    account { name }
  }
}

However, what if you want to know when OTHER people create a new user, so that your UI can update as well. This is the point of subscriptions.

subscription {
  newUsers {
    id
    account { name }
  }
}

The job of the subscription macros then is to give you the tools to connect subscription documents with the values that will drive them. In the last example we would get all users for all accounts, but you could imagine wanting just newUsers(accountId: 2).

In your schema you articulate the interests of a subscription via the config macro:

subscription do
  field :new_users, :user do
    arg :account_id, non_null(:id)

    config fn args, _info ->
      {:ok, topic: args.account_id}
    end
  end
end

The topic can be any term. You can broadcast a value manually to this subscription by doing

Absinthe.Subscription.publish(pubsub, user, [new_users: user.account_id])

It's pretty common to want to associate particular mutations as the triggers for one or more subscriptions, so Absinthe provides some macros to help with that too.

subscription do
  field :new_users, :user do
    arg :account_id, non_null(:id)

    config fn args, _info ->
      {:ok, topic: args.account_id}
    end

    trigger :create_user, topic: fn user ->
      user.account_id
    end
  end
end

The idea with a trigger is that it takes either a single mutation :create_user or a list of mutations [:create_user, :blah_user, ...] and a topic function. This function returns a value that is used to lookup documents on the basis of the topic they returned from the config macro.

Note that a subscription field can have trigger as many trigger blocks as you need, in the event that different groups of mutations return different results that require different topic functions.

Link to this function

to_sdl(schema, opts \\ [])

View Source

Converts a schema to an SDL string

Per the spec, only types that are actually referenced directly or transitively from the root query, subscription, or mutation objects are included.

Example

Absinthe.Schema.to_sdl(MyAppWeb.Schema)
"schema {
  query {...}
}"
@spec types(t()) :: [Absinthe.Type.t()]

List all types on a schema

This function is deprecated. Use Absinthe.Schema.referenced_types/1 instead.
@spec used_types(t()) :: [Absinthe.Type.t()]

Get all types that are used by an operation