All Projects → conradwt → zero-to-graphql-using-elixir

conradwt / zero-to-graphql-using-elixir

Licence: MIT license
The purpose of this example is to provide details as to how one would go about using GraphQL with the Elixir Language.

Programming Languages

elixir
2628 projects
Dockerfile
14818 projects

Projects that are alternatives of or similar to zero-to-graphql-using-elixir

pokerex client
Elm client for PokerEx project
Stars: ✭ 39 (+95%)
Mutual labels:  phoenix, phoenix-framework
Memento
Collect saved items from different sources around the web
Stars: ✭ 89 (+345%)
Mutual labels:  phoenix, phoenix-framework
Elixirbooks
List of Elixir books
Stars: ✭ 1,021 (+5005%)
Mutual labels:  phoenix, phoenix-framework
Remote retro
Free, world-class retrospectives
Stars: ✭ 474 (+2270%)
Mutual labels:  phoenix, phoenix-framework
Tune
A streamlined Spotify client and browser with a focus on performance and integrations.
Stars: ✭ 166 (+730%)
Mutual labels:  phoenix, phoenix-framework
Awesome Phoenix
🔥 Collection of awesome open-source apps made with Phoenix Framework
Stars: ✭ 481 (+2305%)
Mutual labels:  phoenix, phoenix-framework
Shorten api tutorial
🔗How to make a link shortener using Elixir, Phoenix and Mnesia
Stars: ✭ 60 (+200%)
Mutual labels:  phoenix, phoenix-framework
Papercups
Open-source live customer chat
Stars: ✭ 4,554 (+22670%)
Mutual labels:  phoenix, phoenix-framework
Phoenix Liveview Counter Tutorial
🤯 beginners tutorial building a real time counter in Phoenix 1.5.5 + LiveView 0.14.7 ⚡️
Stars: ✭ 115 (+475%)
Mutual labels:  phoenix, phoenix-framework
Elixir Companies
A list of companies currently using Elixir in production.
Stars: ✭ 1,475 (+7275%)
Mutual labels:  phoenix, phoenix-framework
React Phoenix
Make rendering React.js components in Phoenix easy
Stars: ✭ 426 (+2030%)
Mutual labels:  phoenix, phoenix-framework
Realtime
Listen to your to PostgreSQL database in realtime via websockets. Built with Elixir.
Stars: ✭ 4,278 (+21290%)
Mutual labels:  phoenix, phoenix-framework
Hammer
An Elixir rate-limiter with pluggable backends
Stars: ✭ 366 (+1730%)
Mutual labels:  phoenix, phoenix-framework
Guardian auth
The Guardian Authentication Implementation Using Ecto/Postgresql Elixir Phoenix [ User Authentication ]
Stars: ✭ 15 (-25%)
Mutual labels:  phoenix, phoenix-framework
Cercle
Cercle is a CRM+Project Manager for your organization - Phoenix Framework & Vuejs
Stars: ✭ 284 (+1320%)
Mutual labels:  phoenix, phoenix-framework
Phoenix In Action
Code snippets and examples from the book Phoenix in Action from Manning and Geoffrey Lessel
Stars: ✭ 60 (+200%)
Mutual labels:  phoenix, phoenix-framework
game of life-elixir
An implementation of Conway's Game of Life in Elixir
Stars: ✭ 22 (+10%)
Mutual labels:  phoenix, phoenix-framework
phoenix bakery
Better compression for your Phoenix assets
Stars: ✭ 25 (+25%)
Mutual labels:  phoenix, phoenix-framework
Phoenixsharp
C# Phoenix Channels client. Unity Compatible.
Stars: ✭ 96 (+380%)
Mutual labels:  phoenix, phoenix-framework
Phoenix And Elm
Example application using Elixir, Phoenix and Elm
Stars: ✭ 188 (+840%)
Mutual labels:  phoenix, phoenix-framework

Zero to GraphQL Using Elixir

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.

Getting Started

Software requirements

  • Elixir 1.14.3 or newer

  • Erlang 25.2.3 or newer

  • Phoenix 1.6.16 or newer

  • PostgreSQL 14.7 or newer

Note: This tutorial was updated on macOS 12.6.2.

Communication

  • 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.

Quick Installation

  1. clone this repository

    git clone https://github.com/conradwt/zero-to-graphql-using-elixir.git
  2. change directory location

    cd zero-to-graphql-using-elixir
  3. install and compile dependencies

    mix do deps.get, deps.compile
  4. create, migrate, and seed the database

    mix ecto.setup
  5. start the server

    mix phx.server
  6. navigate to our application within the browser

    open http://localhost:4000/graphiql
  7. 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
        }
      }
    }
  8. 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.

Tutorial Installation

  1. 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.

  2. switch to the project directory

    cd zero-to-graphql-using-elixir
  3. update username and password database credentials which appears at the bottom of the following files:

    config/dev.exs
    config/test.exs
    
  4. create the database

    mix ecto.create
  5. generate contexts, schemas, and migrations for the Person resource

    mix phx.gen.context Accounts Person people first_name:string last_name:string username:string email:string
  6. 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
  7. migrate the database

    mix ecto.migrate
  8. generate contexts, schemas, and migrations for the Friendship resource

    mix phx.gen.context Accounts Friendship friendships person_id:references:people friend_id:references:people
  9. 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
  10. 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 the people table because our friend_id really represents a Person schema.

  11. migrate the database

    mix ecto.migrate
  12. create dev support directory

    mkdir -p dev/support
  13. 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"]
  14. 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: "[email protected]",
            username: "conradwt"
          })
    
        dhh =
          Repo.insert!(%Person{
            first_name: "David",
            last_name: "Heinemeier Hansson",
            email: "[email protected]",
            username: "dhh"
          })
    
        ezra =
          Repo.insert!(%Person{
            first_name: "Ezra",
            last_name: "Zygmuntowicz",
            email: "[email protected]",
            username: "ezra"
          })
    
        matz =
          Repo.insert!(%Person{
            first_name: "Yukihiro",
            last_name: "Matsumoto",
            email: "[email protected]",
            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
  15. update the seeds.exs file with the following content:

    priv/repo/seeds.exs:

    ZeroPhoenix.Seeds.run()
  16. seed the database

    mix run priv/repo/seeds.exs
  17. add absinthe, absinthe_plug, and cors_plug hex package dependencies as follows:

    mix.exs:

    defp deps do
      [
        {:phoenix, "~> 1.6.15"},
        {:phoenix_ecto, "~> 4.4"},
        {:ecto_sql, "~> 3.8.3"},
        {:postgrex, "~> 0.16.5"},
        {:phoenix_live_dashboard, "~> 0.6.5"},
        {:swoosh, "~> 1.8.0"},
        {:telemetry_metrics, "~> 0.6.1"},
        {:telemetry_poller, "~> 1.0.0"},
        {:gettext, "~> 0.20.0"},
        {:jason, "~> 1.3.0"},
        {:plug_cowboy, "~> 2.5.2"},
        {:absinthe, "~> 1.7.1"},
        {:absinthe_plug, "~> 1.5.8"},
        {:cors_plug, "~> 3.0.3"}
      ]
    end
  18. install and compile dependencies

    mix do deps.get, deps.compile
  19. 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)
  20. create the GraphQL directory structure

    mkdir -p lib/zero_phoenix_web/graphql/{resolvers,schemas/{queries,mutations},types}
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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 "/api",
        Absinthe.Plug,
        schema: ZeroPhoenixWeb.GraphQL.Schema
    end
  26. start the server

    mix phx.server
  27. navigate to our application within the browser

    open http://localhost:4000/graphiql
  28. 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
        }
      }
    }
  29. 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.

Production Setup

Ready to run in production? Please check our deployment guides.

Phoenix References

GraphQL References

Support

Bug reports and feature requests can be filed with the rest for the Phoenix project here:

License

Zero to GraphQL Using Elixir is released under the MIT license.

Copyright

copyright:: (c) Copyright 2018 - 2023 Conrad Taylor. All Rights Reserved.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].