diff --git a/README.md b/README.md index 4476832..c929983 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,36 @@ -# AbsintheDemo +# GraphQL server with Dataloader Example -To start your Phoenix server: +I've left a trail of [commit messages](https://github.com/denvaar/absinthe_graphql_dataloader/commits/master) that is hopefully easy to follow. I might try to make a video soon. - * Install dependencies with `mix deps.get` - * Create and migrate your database with `mix ecto.setup` - * Install Node.js dependencies with `cd assets && npm install` - * Start Phoenix endpoint with `mix phx.server` +- [Create a new Phoenix app](https://github.com/denvaar/absinthe_graphql_dataloader/commit/5fa2681f8797aa9317e223437eaf6fbd51e39bfb). +- [Install Absinthe and configure it to work with Phoenix](https://github.com/denvaar/absinthe_graphql_dataloader/commit/51bd434b832091ca394ee2f977bdfe63b5e38ab6). +- [Create a database schema with some basic relations; Hook it up to GraphQL schema](https://github.com/denvaar/absinthe_graphql_dataloader/commit/1b523986f5698eeb7c2dfa8dc12dd357e771d245). At this point we have a naive GraphQL schema. The resolvers cause some horrible database queries to happen. +- [Set up Dataloader with Absinthe to improve performance of database queries](https://github.com/denvaar/absinthe_graphql_dataloader/commit/dbbaae5828ffc1a403ce0cea3bc901e048288e0f). -Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. +### Setup Instructions -Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). +1. Install dependencies `mix deps.get` +1. Create database `mix ecto.setup` +1. Create database schema and populate with data `mix ecto.drop; mix ecto.create; mix ecto.migrate; mix run priv/repo/seeds.exs` +1. Start the Phoenix server `mix phx.server` +1. Run GraphQL queries (and browse schema documentation) from your web browser at `http://localhost:4000/api/graphiql` -## Learn more +### Learn more about Dataloader (Efficiently load data in batches) - * Official website: http://www.phoenixframework.org/ - * Guides: https://hexdocs.pm/phoenix/overview.html - * Docs: https://hexdocs.pm/phoenix - * Mailing list: http://groups.google.com/group/phoenix-talk - * Source: https://github.com/phoenixframework/phoenix +- [Elixir Dataloader GitHub repo](https://github.com/absinthe-graphql/dataloader) +- [Elixir Dataloader Hex docs](https://hexdocs.pm/dataloader/Dataloader.html) +- [A helpful blog post](https://schneider.dev/blog/elixir-phoenix-absinthe-graphql-react-apollo-followup/) +- [Original Dataloader JavaScript implementation](https://github.com/graphql/dataloader) + +### Learn more about Absinthe (Elixir implementation of GraphQL spec) + +- [Absinthe GitHub Repo](https://github.com/absinthe-graphql/absinthe) +- [Absinthe Hex docs](https://hexdocs.pm/absinthe/overview.html) + +### Learn more about Phoenix (Elixir Web Framework) + +- [Official website](http://www.phoenixframework.org/) +- [Guides](https://hexdocs.pm/phoenix/overview.html) +- [Docs](https://hexdocs.pm/phoenix) +- [Mailing list](http://groups.google.com/group/phoenix-talk) +- [Source](https://github.com/phoenixframework/phoenix) diff --git a/lib/absinthe_demo_web/data_source.ex b/lib/absinthe_demo_web/data_source.ex new file mode 100644 index 0000000..644aa7b --- /dev/null +++ b/lib/absinthe_demo_web/data_source.ex @@ -0,0 +1,9 @@ +defmodule AbsintheDemo.DataSource do + def data() do + Dataloader.Ecto.new(AbsintheDemo.Repo, query: &query/2) + end + + def query(queryable, _params) do + queryable + end +end diff --git a/lib/absinthe_demo_web/graphql/schema.ex b/lib/absinthe_demo_web/graphql/schema.ex index 25224f3..87638f9 100644 --- a/lib/absinthe_demo_web/graphql/schema.ex +++ b/lib/absinthe_demo_web/graphql/schema.ex @@ -5,6 +5,18 @@ defmodule AbsintheDemoWeb.GraphQL.Schema do alias AbsintheDemoWeb.GraphQL.Resolvers + def context(ctx) do + loader = + Dataloader.new() + |> Dataloader.add_source(:blog_context, AbsintheDemo.DataSource.data()) + + Map.put(ctx, :loader, loader) + end + + def plugins do + [Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults() + end + query do @desc "Get all posts" field :posts, list_of(:post) do diff --git a/lib/absinthe_demo_web/graphql/typedefs.ex b/lib/absinthe_demo_web/graphql/typedefs.ex index 6103fec..ae50627 100644 --- a/lib/absinthe_demo_web/graphql/typedefs.ex +++ b/lib/absinthe_demo_web/graphql/typedefs.ex @@ -1,6 +1,8 @@ defmodule AbsintheDemoWeb.GraphQL.Typedefs do use Absinthe.Schema.Notation + import Absinthe.Resolution.Helpers, only: [dataloader: 1] + alias AbsintheDemo.Database object :post do @@ -8,19 +10,9 @@ defmodule AbsintheDemoWeb.GraphQL.Typedefs do field :title, :string field :body, :string - field :author, :author do - resolve(fn post, _args, _info -> - # Example of N+1 problem - {:ok, Database.get_author(post.author_id)} - end) - end - - field :categories, list_of(:category) do - resolve(fn post, _args, _info -> - # Example of N+1 problem - {:ok, Database.get_categories_of_post(post.id)} - end) - end + field :author, :author, resolve: dataloader(:blog_context) + + field :categories, list_of(:category), resolve: dataloader(:blog_context) end object :author do @@ -28,23 +20,13 @@ defmodule AbsintheDemoWeb.GraphQL.Typedefs do field :name, :string field :profile_picture_link, :string - field :posts, list_of(:post) do - resolve(fn author, _args, _info -> - # Example of N+1 problem - {:ok, Database.get_posts_of_author(author.id)} - end) - end + field :posts, list_of(:post), resolve: dataloader(:blog_context) end object :category do field :id, :id field :name, :string - field :posts, list_of(:post) do - resolve(fn category, _args, _info -> - # Example of N+1 problem - {:ok, Database.get_posts_of_category(category.id)} - end) - end + field :posts, list_of(:post), resolve: dataloader(:blog_context) end end diff --git a/mix.exs b/mix.exs index d7471d5..453c6ee 100644 --- a/mix.exs +++ b/mix.exs @@ -45,7 +45,8 @@ defmodule AbsintheDemo.MixProject do {:plug_cowboy, "~> 2.0"}, # Absinthe GraphQL stuff {:absinthe, "~> 1.4.16"}, - {:absinthe_phoenix, "~> 1.4.4"} + {:absinthe_phoenix, "~> 1.4.4"}, + {:dataloader, "~> 1.0.7"} ] end diff --git a/mix.lock b/mix.lock index 299add7..03ede62 100644 --- a/mix.lock +++ b/mix.lock @@ -5,6 +5,7 @@ "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"}, + "dataloader": {:hex, :dataloader, "1.0.7", "58351b335673cf40601429bfed6c11fece6ce7ad169b2ac0f0fe83e716587391", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "3.3.4", "95b05c82ae91361475e5491c9f3ac47632f940b3f92ae3988ac1aad04989c5bb", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},