Absinthe

A GraphQL implementation for Elixir.

Build Status

Experimental!

Please note that this is an initial release, and while functional enough to build basic APIs (we are using it in a production system), it should be considered experimental. (Notably, it does not yet work with Relay.)

For more information on status, see Specification Implementation, below.

Goal

Absinthe's goal is full implementation of the specification--in as idiomatic, flexible, and comfortable way possible.

Working Features

Notably Missing

Support for:

Alternatives

You may also want to look at building from or using one of the following alternatives.

Installation

Install from Hex.pm:

def deps do
  [{:absinthe, "~> 0.1.0"}]
end

Note: Absinthe requires Elixir 1.2.0-dev or higher.

Learning GraphQL

For a grounding in GraphQL, I recommend you read through the following articles:

You may also be interested in how GraphQL is used by Relay, a "JavaScript frameword for building data-driven React applications."

Basic Usage

A GraphQL API starts by building a schema. Using Absinthe, schemas are normal modules that use Absinthe.Schema and adhere to its behavior (ie, define at least query).

For this example, we'll build a simple schema that allows users to look-up an item by id, a required, non-null field of type :id (which is a built-in type, just like :string, :integer, :float, and :boolean).

(You may want to refer to the Absinthe API documentation for more detailed information as you look this over.)

defmodule MyApp.Schema do

  use Absinthe.Schema

  alias Absinthe.Type

  # Example data
  @items %{
    "foo" => %{id: "foo", name: "Foo"},
    "bar" => %{id: "bar", name: "Bar"}
  }

  def query do
    %Type.ObjectType{
      fields: fields(
        item: [
          type: :item
          args: args(
            id: [type: non_null(:id)]
          ),
          resolve: fn %{id: item_id}, _ ->
            {:ok, @items[item_id]}
          end
        ]
      )
    }
  end

end

Some functions used here that are worth mentioning, pulled in automatically from Absinthe.Type.Definitions by use Absinthe.Schema:

You'll notice we mention another type here: :item.

We haven't defined that yet; let's do it. In the same MyApp.Schema module:

@absinthe :type
def item do
  %Type.ObjectType{
    description: "An item",
    fields: fields(
      id: [type: :id],
      name: [type: :string]
    )
  }
end

Some notes on defining types:

See the documentation for Absinthe.Type.Definitions for more information.

Now, you can use Absinthe to execute a query document. Let's get the item with ID "foo":

"""
{
  item(id: "foo") {
    name
  }
}
"""
|> Absinthe.run(MyApp.Schema)

# Result
{:ok, %{data: %{"item" => %{"name" => "Foo"}}}}

We can also use a variable:

Variables

To support variables, simply define them for your query document as the specification expects, and pass in a variables option (eg, query parameters passed along with the request) to run:

"""
query GetItem($id: ID!) {
  item(id: $id) {
    name
  }
}
"""
|> Absinthe.run(MyApp.Schema, variables: %{id: "bar"})

# Result
{:ok, %{data: %{"item" => %{"name" => "Bar"}}}}

Deprecation

Use the deprecate function on an argument definition (or input object field), passing an optional reason:

def query do
  %Type.ObjectType{
    name: "RootQuery",
    fields: fields(
      item: [
        type: :item
        args: args(
          id: [type: non_null(:id)],
          oldId: deprecate([type: non_null(:string)],
                           reason: "It's old.")
        ),
        resolve: fn %{id: item_id}, _ ->
          {:ok, @items[item_id]}
        end
      ]
    )
  }
end

resolve functions must accept 2 arguments: a map of arguments and a special %Absinthe.Execution{} struct that provides the full execution context (useful for advanced purposes). resolve functions must return a {:ok, result} or {:error, "Error to report"} tuple.

Note: At the current time, Absinthe reports any deprecated argument or deprecated input object field used in the errors entry of the response. Non null constraints are ignored when validating deprecated arguments and input object fields.

Custom Types

Absinthe supports defining custom scalar types, just like the built-in types. Here's an example of how to support a time scalar to/from ISOz format:

@absinthe type: :iso_z
def iso_z_type do
  %Type.Scalar{
    name: "ISOz",
    description: "ISOz time",
    parse: &Timex.DateFormat.parse(&1, "{ISOz}"),
    serialize: &Timex.DateFormat.format!(&1, "{ISOz}")
  }
end

Now :iso_z can be used in your schema and variables can use ISOz in query documents.

Adapters

Absinthe supports an adapter mechanism that allows developers to define their schema using one code convention (eg, snake_cased fields and arguments), but accept query documents and return results (including names in errors) in another (eg, camelCase). This is useful in allowing both client and server to use conventions most natural to them.

Absinthe ships with two adapters:

To set the adapter, you can set an application configuration value:

config :absinthe,
  adapter: Absinthe.Adapter.LanguageConventions

Or, you can provide it as an option to Absinthe.run/3:

Absinthe.run(query, MyApp.Schema,
             adapter: Absinthe.Adapter.LanguageConventions)

Notably, this means you're able to switch adapters on case-by-case basis. In a Phoenix application, this means you could even support using different adapters for different clients.

A custom adapter module must merely implement the Absinthe.Adapter protocol, in many cases with use Absinthe.Adapter and only overriding the desired functions.

Specification Implementation

Absinthe is currently targeting the GraphQL Working Draft, dated October 2015.

Here's the basic status, using the following scale:

Section Implementation Reference
Language Functional GraphQL Specification, Section 2
Type System Functional GraphQL Specification, Section 3
Introspection Missing GraphQL Specification, Section 4
Validation Partial GraphQL Specification, Section 5
Execution Functional GraphQL Specification, Section 6
Response Functional GraphQL Specification, Section 7

Roadmap & Contributions

For a list of specific planned features and version targets, see the milestone list.

We welcome issues and pull requests; please see CONTRIBUTING.

License

BSD License

Copyright (c) CargoSense, Inc.

Parser derived from GraphQL Elixir, Copyright (c) Josh Price https://github.com/joshprice/graphql-elixir

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.