apigenie-generatorGraphQLserver absinthe
server absinthe
Generate an Absinthe server from a GraphQL schema (SDL).
Usage
apigenie-generator server absinthe [...OPTIONS] INPUT_FILE
The input is a GraphQL schema: a .graphql, .graphqls or .gql file. It must be valid and
self-contained, and have a query type.
Description
The generator writes a Mix project (Elixir 1.18 or later) that depends on Absinthe 1.12,
Absinthe.Plug, Plug, Bandit and Jason. The code passes mix format --check-formatted,
mix compile --warnings-as-errors and mix credo --strict, and mix run --no-halt serves it.
| File | Contents |
|---|---|
priv/graphql/schema.graphql |
the schema, as written |
lib/<app>/schema.ex |
the schema module: import_sdl of the schema, and hydrate/2 attaching resolvers, type resolution and scalars |
lib/<app>/schema/directives.ex |
the custom directives of the schema, declared in a prototype schema |
lib/<app>/resolvers/<type>.ex |
a behaviour per object type with root fields or fields with arguments, with a callback per field |
lib/<app>/resolvers.ex |
the lookup of the configured implementations |
lib/<app>/type_resolver.ex |
the object types of the values of interfaces and unions |
lib/<app>/scalars.ex |
the parsing and serialization of the custom scalars |
lib/<app>/loader.ex |
the Dataloader source, with the dataloader option |
lib/<app>/router.ex |
the Plug router: the GraphQL endpoint, and GraphiQL in the dev environment |
lib/<app>/application.ex |
the application, starting Bandit |
The schema
<Root>.Schema imports the schema from priv/graphql/schema.graphql with import_sdl, and
recompiles when the file changes. Absinthe keeps everything the schema says:
- descriptions of types, fields, arguments, enum values and input fields;
- deprecations of fields, arguments, input fields and enum values;
- default values, interfaces implementing interfaces, and
@oneOfinput types, which it enforces; - type extensions.
Two things need code next to the SDL:
- Custom directives need a prototype schema. Absinthe applies only the directives its
prototype schema declares, so
<Root>.Schema.Directivesdeclares the directives of the schema, along with the enums their arguments use. - Custom scalars need parse and serialize functions, which
hydrate/2attaches.
Resolvers and implementations
Root fields, and fields with arguments, are resolved by the module of their type:
<Root>.Resolvers.Query, <Root>.Resolvers.Mutation, <Root>.Resolvers.Warehouse… Each one is
a behaviour with a callback per field:
- a root field’s callback receives the arguments (a map with atom keys, as Absinthe gives them) and the resolution;
- a nested field’s callback receives the parent value first.
@callback track_shipment(args :: track_shipment_args(), resolution :: Absinthe.Resolution.t()) ::
Resolvers.result()
Implement the behaviours and configure the implementations in config/config.exs. Regenerating
never overwrites them:
config :carpet_express, CarpetExpress.Resolvers.Query, impl: MyApp.Query
A callback returns {:ok, value} or {:error, reason}. Fields without an implementation answer
an error (“Query.trackShipment is not implemented”). The other fields read their value from the
parent value with Absinthe’s default resolution: the key that is the snake_case name of the field,
in a map or a struct.
Interfaces and unions
<Root>.TypeResolver resolves the object type of a value of an interface or a union:
- a map with a
__typenamekey resolves to the type it names; - other values go to the
resolve_type(type, value, resolution)callback of the implementation configured for<Root>.TypeResolver.
Scalars
<Root>.Scalars has a parse_<scalar>/1 and a serialize_<scalar>/1 function per custom scalar.
Until an implementation of its parse/2 and serialize/2 callbacks is configured, they pass
string, number and boolean literals and values through as they are.
Router and GraphiQL
<Root>.Router parses JSON, form and multipart bodies (Absinthe.Plug.Parser) and forwards the
endpoint’s path (/graphql) to Absinthe.Plug, which takes queries and mutations by POST and GET.
When the application’s :graphiql setting is true, as config/dev.exs sets it, the router also
serves GraphiQL at /graphiql. Bandit listens on 127.0.0.1:4000 in development and on all
interfaces in production; the PORT environment variable sets the port.
Dataloader
With dataloader: true, context/1 puts a Dataloader in the context of the resolvers
(resolution.context.loader), with a Dataloader.KV source named <Root>.Loader. Its batches go
to the fetch(batch_key, ids) callback of the implementation configured for <Root>.Loader.
Limitations
- Subscriptions get their resolvers, but the application has no subscription transport (Absinthe.Phoenix channels), so they aren’t served.
- Absinthe’s introspection doesn’t expose the
specifiedByURLof scalars, although the schema keeps@specifiedBy. - Absinthe can’t import extensions of a root type that the schema definition gives another name
(
schema { query: Root }withextend type Root). The generator reports this as an error. - The arguments of custom directives can only have built-in scalar and enum types.
Options
-c, –config
Path to the configuration file to use. See the “Configuration” section for more information.
–no-config
Doesn’t look for apigenie.yaml (or .apigenie.yaml) in the current directory and its parents.
-o, –output
Directory where the project is generated.
–package-name
Name of the Mix application, converted to snake_case. carpet-express gives the application
carpet_express and the root module CarpetExpress. Defaults to the name of the output directory.
–package-version
Version of the Mix project; 0.1.0 by default.
-p, –property NAME=VALUE
Sets a generator option (see below), e.g. -p dataloader=true. Options given on the command line
take precedence over the configuration file.
Configuration
version: '1.0'
commands:
generate:
server_absinthe:
app: carpet_express
path: /api/graphql
dataloader: true
| Option | Type | Default | Description |
|---|---|---|---|
app |
string | name of the output directory | Name of the Mix application (snake_case, the directory under lib/); also set by --package-name. |
namespace |
string | the application in PascalCase | Root module of the generated modules, e.g. CarpetExpress. |
path |
string | /graphql |
Path of the GraphQL endpoint. |
graphiql_path |
string | /graphiql |
Path of GraphiQL, served in the dev environment. |
dataloader |
boolean | false |
Adds a Dataloader to the context of the resolvers. |
dev_deps |
boolean | true |
Lists credo as a dependency of mix.exs (only: [:dev, :test]). |