The purpose of this example is to provide details as to how one would go about using GraphQL with the Elixir Language. Thus, I have created two major sections which should be self explanatory: Quick Installation and Tutorial Installation.
-
Elixir 1.17.3 or newer
-
Erlang 27.1.2 or newer
-
Phoenix 1.7.14 or newer
-
PostgreSQL 16.4 or newer
Note: This tutorial was updated on macOS 15.0.1.
- If you need help, use Stack Overflow. (Tag 'absinthe')
- If you'd like to ask a general question, use Stack Overflow.
- If you found a bug, open an issue.
- If you have a feature request, open an issue.
- If you want to contribute, submit a pull request.
-
clone this repository
git clone https://github.com/conradwt/zero-to-graphql-using-elixir.git
-
change directory location
cd zero-to-graphql-using-elixir
-
install and compile dependencies
mix do deps.get, deps.compile
-
create, migrate, and seed the database
mix ecto.setup
-
start the server
mix phx.server
-
navigate to our application within the browser
open http://localhost:4000/graphiql
-
enter the below GraphQL query on the left side of the browser window
{ person(id: 1) { firstName lastName username email friends { firstName lastName username email } } }
-
run the GraphQL query
Control + Enter
Note: The GraphQL query is responding with same response but different shape within the GraphiQL browser because Elixir Maps perform no ordering on insertion.
-
create the project
mix phx.new zero-to-graphql-using-elixir \ --app zero_phoenix \ --module ZeroPhoenix \ --no-html \ --no-assets
Note: Just answer 'y' to all the prompts that appear.
-
switch to the project directory
cd zero-to-graphql-using-elixir
-
update
username
andpassword
database credentials which appears at the bottom of the following files:config/dev.exs config/test.exs
-
create the database
mix ecto.create
-
generate contexts, schemas, and migrations for the
Person
resourcemix phx.gen.context Accounts Person people first_name:string last_name:string username:string email:string
-
replace the generated
Person
schema with the following:lib/zero_phoenix/account/person.ex
:defmodule ZeroPhoenix.Accounts.Person do use Ecto.Schema import Ecto.Changeset alias ZeroPhoenix.Accounts.Friendship alias ZeroPhoenix.Accounts.Person schema "people" do field :email, :string field :first_name, :string field :last_name, :string field :username, :string has_many :friendships, Friendship has_many :friends, through: [:friendships, :friend] timestamps() end @doc false def changeset(%Person{} = person, attrs) do person |> cast(attrs, [:first_name, :last_name, :username, :email]) |> validate_required([:first_name, :last_name, :username, :email]) end end
-
migrate the database
mix ecto.migrate
-
generate contexts, schemas, and migrations for the
Friendship
resourcemix phx.gen.context Accounts Friendship friendships person_id:references:people friend_id:references:people
-
replace the generated
CreateFriendship
migration with the following:priv/repo/migrations/<some datetime>_create_friendship.exs
:defmodule ZeroPhoenix.Repo.Migrations.CreateFriendship do use Ecto.Migration def change do create table(:friendships) do add :person_id, references(:people, on_delete: :delete_all) add :friend_id, references(:people, on_delete: :delete_all) timestamps() end create index(:friendships, [:person_id]) create index(:friendships, [:friend_id]) end end
-
replace the generated
Friendship
schema with the following:lib/zero_phoenix/accounts/friendship.ex
:defmodule ZeroPhoenix.Accounts.Friendship do use Ecto.Schema import Ecto.Changeset alias ZeroPhoenix.Accounts.Friendship alias ZeroPhoenix.Accounts.Person @required_fields [:person_id, :friend_id] schema "friendships" do belongs_to :person, Person belongs_to :friend, Person timestamps() end @doc false def changeset(%Friendship{} = friendship, attrs) do friendship |> cast(attrs, @required_fields) |> validate_required(@required_fields) end end
Note: We want
friend_id
to reference thepeople
table because ourfriend_id
really represents aPerson
schema. -
migrate the database
mix ecto.migrate
-
create dev support directory
mkdir -p dev/support
-
update the search for compiler within
mix.exs
:change:
defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"]
to:
defp elixirc_paths(:test), do: ["lib", "dev/support", "test/support"] defp elixirc_paths(:dev), do: ["lib", "dev/support"] defp elixirc_paths(_), do: ["lib"]
-
create
seeds.ex
support file with the following content:dev/support/seeds.ex
:defmodule ZeroPhoenix.Seeds do alias ZeroPhoenix.Accounts.{Person, Friendship} alias ZeroPhoenix.Repo def run() do # # reset database # reset() # # create people # me = Repo.insert!(%Person{ first_name: "Conrad", last_name: "Taylor", email: "conradwt@gmail.com", username: "conradwt" }) dhh = Repo.insert!(%Person{ first_name: "David", last_name: "Heinemeier Hansson", email: "dhh@37signals.com", username: "dhh" }) ezra = Repo.insert!(%Person{ first_name: "Ezra", last_name: "Zygmuntowicz", email: "ezra@merbivore.com", username: "ezra" }) matz = Repo.insert!(%Person{ first_name: "Yukihiro", last_name: "Matsumoto", email: "matz@heroku.com", username: "matz" }) # # create friendships # me |> Ecto.build_assoc(:friendships) |> Friendship.changeset(%{friend_id: matz.id}) |> Repo.insert() dhh |> Ecto.build_assoc(:friendships) |> Friendship.changeset(%{friend_id: ezra.id}) |> Repo.insert() dhh |> Ecto.build_assoc(:friendships) |> Friendship.changeset(%{friend_id: matz.id}) |> Repo.insert() ezra |> Ecto.build_assoc(:friendships) |> Friendship.changeset(%{friend_id: dhh.id}) |> Repo.insert() ezra |> Ecto.build_assoc(:friendships) |> Friendship.changeset(%{friend_id: matz.id}) |> Repo.insert() matz |> Ecto.build_assoc(:friendships) |> Friendship.changeset(%{friend_id: me.id}) |> Repo.insert() matz |> Ecto.build_assoc(:friendships) |> Friendship.changeset(%{friend_id: ezra.id}) |> Repo.insert() matz |> Ecto.build_assoc(:friendships) |> Friendship.changeset(%{friend_id: dhh.id}) |> Repo.insert() :ok end def reset do Person |> Repo.delete_all() end end
-
update the
seeds.exs
file with the following content:priv/repo/seeds.exs
:ZeroPhoenix.Seeds.run()
-
seed the database
mix run priv/repo/seeds.exs
-
add
absinthe
,absinthe_plug
, andcors_plug
hex package dependencies as follows:mix.exs
:defp deps do [ {:phoenix, "~> 1.7.14"}, {:phoenix_ecto, "~> 4.4.3"}, {:ecto_sql, "~> 3.10.1"}, {:postgrex, "~> 0.17.5"}, {:phoenix_live_dashboard, "~> 0.7.2"}, {:swoosh, "~> 1.11.6"}, {:finch, "~> 0.16.0"}, {:telemetry_metrics, "~> 0.6.2"}, {:telemetry_poller, "~> 1.0.0"}, {:gettext, "~> 0.22.3"}, {:jason, "~> 1.4.4"}, {:bandit, "~> 1.3.0"}, {:absinthe, "~> 1.7.8"}, {:absinthe_plug, "~> 1.5.8"}, {:cors_plug, "~> 3.0.3"} ] end
-
install and compile dependencies
mix do deps.get, deps.compile
-
configure
CORSPlug
by adding the following content:lib/zero_phoenix_web/endpoint.ex
:plug CORSPlug, origin: ["*"]
Note: The above code should be added right before the following line:
plug(ZeroPhoenixWeb.Router)
-
create the GraphQL directory structure
mkdir -p lib/zero_phoenix_web/graphql/{resolvers,schemas/{queries,mutations},types}
-
add the GraphQL schema which represents our entry point into our GraphQL structure:
lib/zero_phoenix_web/graphql/schema.ex
:defmodule ZeroPhoenixWeb.GraphQL.Schema do use Absinthe.Schema import_types(ZeroPhoenixWeb.GraphQL.Types.Person) import_types(ZeroPhoenixWeb.GraphQL.Schemas.Queries.Person) query do import_fields(:person_queries) end end
-
add our Person type which will be performing queries against:
lib/zero_phoenix_web/graphql/types/person.ex
:defmodule ZeroPhoenixWeb.GraphQL.Types.Person do use Absinthe.Schema.Notation import Ecto alias ZeroPhoenix.Repo @desc "a person" object :person do @desc "unique identifier for the person" field :id, non_null(:string) @desc "first name of a person" field :first_name, non_null(:string) @desc "last name of a person" field :last_name, non_null(:string) @desc "username of a person" field :username, non_null(:string) @desc "email of a person" field :email, non_null(:string) @desc "a list of friends for our person" field :friends, list_of(:person) do resolve fn _, %{source: person} -> {:ok, Repo.all(assoc(person, :friends))} end end end end
-
add the
person_queries
object to contain all the queries for a person:lib/zero_phoenix_web/graphql/schemas/queries/person.ex
:defmodule ZeroPhoenixWeb.GraphQL.Schemas.Queries.Person do use Absinthe.Schema.Notation object :person_queries do field :person, type: :person do arg :id, non_null(:id) resolve(&ZeroPhoenixWeb.GraphQL.Resolvers.PersonResolver.find/3) end end end
-
add the
PersonResolver
to fetch the individual fields of our person object:lib/zero_phoenix_web/graphql/resolvers/person_resolver.ex
:defmodule ZeroPhoenixWeb.GraphQL.Resolvers.PersonResolver do alias ZeroPhoenix.Accounts alias ZeroPhoenix.Accounts.Person def find(_parent, %{id: id}, _info) do case Accounts.get_person(id) do %Person{} = person -> {:ok, person} _error -> {:error, "Person id #{id} not found"} end end end
-
add routes for our GraphQL API and GraphiQL browser endpoints:
lib/zero_phoenix_web/router.ex
:replace
scope "/api", ZeroPhoenixWeb do pipe_through :api end
with
scope "/" do pipe_through :api if Mix.env() in [:dev, :test] do forward "/graphiql", Absinthe.Plug.GraphiQL, schema: ZeroPhoenixWeb.GraphQL.Schema, json_codec: Jason, interface: :playground end forward "/graphql", Absinthe.Plug, schema: ZeroPhoenixWeb.GraphQL.Schema end
-
start the server
mix phx.server
-
navigate to our application within the browser
open http://localhost:4000/graphiql
-
enter the below GraphQL query on the left side of the browser window
{ person(id: 1) { firstName lastName username email friends { firstName lastName username email } } }
-
run the GraphQL query
Control + Enter
Note: The GraphQL query is responding with same response but different shape within the GraphiQL browser because Elixir Maps perform no ordering on insertion.
Ready to run in production? Please check our deployment guides.
- Official website: http://www.phoenixframework.org/
- Guides: http://phoenixframework.org/docs/overview
- Docs: https://hexdocs.pm/phoenix
- Mailing list: http://groups.google.com/group/phoenix-talk
- Source: https://github.com/phoenixframework/phoenix
- Official Website: http://graphql.org
- Absinthe GraphQL Elixir: http://absinthe-graphql.org
Bug reports and feature requests can be filed with the rest for the Phoenix project here:
Zero to GraphQL Using Elixir is released under the MIT license.
Copyright © 2018 - 2024 Conrad Taylor. All rights reserved.