Skip to content

Commit 5ca4a08

Browse files
committed
AshGraphql Example
1 parent 9884701 commit 5ca4a08

File tree

7 files changed

+185
-8
lines changed

7 files changed

+185
-8
lines changed

.formatter.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
:ash_postgres,
99
:ash_authentication,
1010
:ash_authentication_phoenix,
11-
:ash_json_api
11+
:ash_json_api,
12+
:ash_graphql
1213
],
1314
subdirectories: ["priv/*/migrations"],
1415
plugins: [Phoenix.LiveView.HTMLFormatter],

README.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ Elixir runs on the Erlang VM which has many examples of scalability
3838
- With LiveView developers can write reactive single-page frontends in HTML and Elixir. (No Javascript)
3939
- Ash lets you define a model and then with minimal code derive database persistance, code wrappers, REST API, GraphQL API, and more.
4040

41+
## PETAL Stack Q/A
42+
43+
### Q: Do I have to use Ash?
44+
45+
No. Ash is not needed. But I recommend Ash because it puts a lot of complexity up front and pays dividends later. With Ecto, the default database library for Phoenix, you declare the schema and only derive Database persistance and some validation. With Ash you declare the schema and get the following:
46+
47+
- Resource and Domain level functions
48+
- Database persistance (Postgres, SQLite3)
49+
- alternative persistance (in memory, ETS table, Mnesia table, CSV file)
50+
- Form validation
51+
- JSON API mapping
52+
- GraphQL API mapping
53+
- and more with a robust extension system ...
54+
4155
## Commit References
4256

4357
This is a summary of notable commits, and sections that go into that code in greater detail.
@@ -294,6 +308,8 @@ defp deps do
294308
end
295309
```
296310

311+
Note: `AshPostgres` is built on top of `Ecto`, a database library for Elixir. `Ecto` is what Phoenix uses by default for communicating with the database.
312+
297313
Optionally, make the changes to `.formatter.exs`
298314
```elixir
299315
[
@@ -678,3 +694,109 @@ For the sake of completeness, next I want to show how to add a GraphQL API.
678694
We can add a GraphQL API for the Blog domain.
679695

680696
I followed [this getting started guide](https://ash-hq.org/docs/guides/ash_graphql/latest/tutorials/getting-started-with-graphql) for adding `AshGraphql`.
697+
698+
Add the `ash_json_api` dependency:
699+
```elixir
700+
defp deps do
701+
[
702+
# ...
703+
{:ash_graphql, "~> 1.1.1"},
704+
# ...
705+
]
706+
end
707+
```
708+
709+
Note: `AshGraphql` is built on top of `Absinthe`, a GraphQL library for Elixir.
710+
711+
Add to `.formatter.exs`:
712+
```elixir
713+
[
714+
import_deps: [
715+
# ...
716+
:ash_graphql
717+
],
718+
#...
719+
]
720+
```
721+
722+
Add the `AshGraphql.Domain` extension to the domain. Also, disable authorization for easy prototyping.
723+
```elixir
724+
defmodule PetalStackTutorial.Blog do
725+
use Ash.Domain,
726+
extensions: [
727+
# ...
728+
AshGraphql.Domain # <-- add this
729+
]
730+
731+
# ...
732+
graphql do
733+
authorize? false
734+
end
735+
end
736+
```
737+
738+
Add the `AshGraphql.Domain` extension to the resource. Then specify
739+
```elixir
740+
defmodule PetalStackTutorial.Blog.Post do
741+
use Ash.Resource,
742+
domain: PetalStackTutorial.Blog,
743+
data_layer: AshPostgres.DataLayer,
744+
extensions: [
745+
AshJsonApi.Resource,
746+
AshGraphql.Resource
747+
]
748+
749+
graphql do
750+
end
751+
end
752+
```
753+
754+
Create a schema.
755+
```elixir
756+
defmodule PetalStackTutorialWeb.GraphqlSchema do
757+
use Absinthe.Schema
758+
use AshGraphql, domains: [PetalStackTutorial.Blog]
759+
760+
query do
761+
end
762+
763+
mutation do
764+
end
765+
end
766+
```
767+
768+
Add the schema to `router.ex`. Use the `Absinthe.Plug` with your schema to serve the main GraphQL API. Also use the `Absinthe.Plug.GraphiQL` to create an interactive version of graphql
769+
```elixir
770+
defmodule PetalStackTutorialWeb.Router do
771+
use PetalStackTutorialWeb, :router
772+
# ...
773+
pipeline :graphql do
774+
plug AshGraphql.Plug
775+
end
776+
# ...
777+
778+
scope "/gql" do
779+
pipe_through :graphql
780+
781+
forward "/playground",
782+
Absinthe.Plug.GraphiQL,
783+
schema: PetalStackTutorialWeb.GraphqlSchema,
784+
interface: :playground
785+
786+
forward "/", Absinthe.Plug, schema: PetalStackTutorialWeb.GraphqlSchema
787+
end
788+
end
789+
```
790+
791+
Now when I visit http://localhost:4000/api/gql/playground it shows an interactive version of GraphQL. There is a "SCHEMA" tab on the right that contains all queries and mutations.
792+
793+
I can make the following query:
794+
```graphql
795+
query {
796+
getPost(id: "7e9cc199-7c5f-4196-bb7c-d28550d6b7c2") {
797+
id
798+
}
799+
}
800+
```
801+
802+
### Section 7: TBD
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
defmodule PetalStackTutorial.Blog do
2-
use Ash.Domain, extensions: [
3-
AshJsonApi.Domain
4-
]
2+
use Ash.Domain,
3+
extensions: [
4+
AshJsonApi.Domain,
5+
AshGraphql.Domain
6+
]
57

68
resources do
79
resource PetalStackTutorial.Blog.Post do
@@ -12,4 +14,8 @@ defmodule PetalStackTutorial.Blog do
1214
define :get_post, action: :get, args: [:id]
1315
end
1416
end
17+
18+
graphql do
19+
authorize? false
20+
end
1521
end

lib/petal_stack_tutorial/blog/post.ex

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ defmodule PetalStackTutorial.Blog.Post do
33
domain: PetalStackTutorial.Blog,
44
data_layer: AshPostgres.DataLayer,
55
extensions: [
6-
AshJsonApi.Resource
6+
AshJsonApi.Resource,
7+
AshGraphql.Resource
78
]
89

910
postgres do
@@ -57,4 +58,19 @@ defmodule PetalStackTutorial.Blog.Post do
5758
delete :destroy
5859
end
5960
end
61+
62+
graphql do
63+
type :post
64+
65+
queries do
66+
get :get_post, :read
67+
list :list_posts, :read
68+
end
69+
70+
mutations do
71+
create :create_post, :create
72+
update :update_post, :update
73+
destroy :destroy_post, :destroy
74+
end
75+
end
6076
end

lib/petal_stack_tutorial_web/router.ex

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ defmodule PetalStackTutorialWeb.Router do
1717
plug :load_from_bearer
1818
end
1919

20+
pipeline :graphql do
21+
plug AshGraphql.Plug
22+
end
23+
2024
scope "/", PetalStackTutorialWeb do
2125
pipe_through :browser
2226

@@ -32,10 +36,22 @@ defmodule PetalStackTutorialWeb.Router do
3236
reset_route []
3337
end
3438

35-
scope "/api/json" do
36-
pipe_through :api
39+
scope "/api" do
40+
scope "/json" do
41+
pipe_through :api
42+
forward "/", PetalStackTutorialWeb.JsonApiRouter
43+
end
44+
45+
scope "/gql" do
46+
pipe_through :graphql
47+
48+
forward "/playground",
49+
Absinthe.Plug.GraphiQL,
50+
schema: PetalStackTutorialWeb.GraphqlSchema,
51+
interface: :playground
3752

38-
forward "/", PetalStackTutorialWeb.JsonApiRouter
53+
forward "/", Absinthe.Plug, schema: PetalStackTutorialWeb.GraphqlSchema
54+
end
3955
end
4056

4157
# Enable LiveDashboard and Swoosh mailbox preview in development
@@ -68,3 +84,14 @@ defmodule PetalStackTutorialWeb.JsonApiRouter do
6884
json_schema: "/json_schema",
6985
open_api: "/open_api"
7086
end
87+
88+
defmodule PetalStackTutorialWeb.GraphqlSchema do
89+
use Absinthe.Schema
90+
use AshGraphql, domains: [PetalStackTutorial.Blog]
91+
92+
query do
93+
end
94+
95+
mutation do
96+
end
97+
end

mix.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ defmodule PetalStackTutorial.MixProject do
4040
{:ash_authentication_phoenix, "~> 2.0"},
4141
{:ash_json_api, "~> 1.0"},
4242
{:open_api_spex, "~> 3.16"},
43+
{:ash_graphql, "~> 1.1.1"},
4344

4445
# phoenix
4546
{:phoenix, "~> 1.7.12"},

mix.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
%{
2+
"absinthe": {:hex, :absinthe, "1.7.6", "0b897365f98d068cfcb4533c0200a8e58825a4aeeae6ec33633ebed6de11773b", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7626951ca5eec627da960615b51009f3a774765406ff02722b1d818f17e5778"},
3+
"absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"},
24
"ash": {:hex, :ash, "3.0.9", "f98266488f9f152130a6015c7158f70ba8262939c4dd0728bb55ab48b6d282eb", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "adb0853558c13cd77a4489e755df149e0e43bde9c069811ec244dd5dcbe62cd0"},
35
"ash_authentication": {:hex, :ash_authentication, "4.0.0", "d723633301512ef8bb14b84294139a31562b49b7cb568636f914e12465ab27ba", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_postgres, "~> 2.0", [hex: :ash_postgres, repo: "hexpm", optional: true]}, {:assent, ">= 0.2.8 and < 1.0.0-0", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:finch, "~> 0.18.0", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}], "hexpm", "9da852e60c89596e35363f7866f6500bad1639b5e8c626a77ccad20d40db91f5"},
46
"ash_authentication_phoenix": {:hex, :ash_authentication_phoenix, "2.0.0", "1d2dd0abc9b9e008ea4423e902eb24825dbf4b9d1329bd079d7064ecfc45d319", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, "~> 4.0", [hex: :ash_authentication, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 2.0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "6a7c24d57ef6f7a4456d5ba139c8221df6a7ed81f15707a23fc33ad369e43a36"},
7+
"ash_graphql": {:hex, :ash_graphql, "1.1.1", "91f15ea0da300f496f131fb36b14e1b734e465f66c20d2ba85886735b70bab9d", [:mix], [{:absinthe, "~> 1.7", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.4", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "16d6fdfe911087f22968257f15e312fc597c0cc1778d56bc5a6699b55ff987f0"},
58
"ash_json_api": {:hex, :ash_json_api, "1.1.0", "86d38bb810ae06170bea2ead3dd558cacb5c760f7db62356d9280dc996093814", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4", [hex: :json_xema, repo: "hexpm", optional: false]}, {:open_api_spex, "~> 3.16", [hex: :open_api_spex, repo: "hexpm", optional: true]}, {:plug, "~> 1.11", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0db08bc19ac52d3adb31df5a2090cfa0da2937b413b9f4cd7738b0a876200454"},
69
"ash_phoenix": {:hex, :ash_phoenix, "2.0.2", "38f62e3cfbaf027467662f32b5597627d04c39a1a5e1c59399f7712209718d1b", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6 or ~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.3 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "fc7ee7bf3bbc8abe36e4e940d754bd326aa360092d01f64095fbbb31fe3fe606"},
710
"ash_postgres": {:hex, :ash_postgres, "2.0.6", "9c504453e2327db8a27c8333b0a62c2e4940b4f769c7f5b4fa14b9cfb5debf15", [:mix], [{:ash, ">= 3.0.7 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_sql, "~> 0.2", [hex: :ash_sql, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "9b096d4ba605bcf6909fa04f4dee3f982141b89822bb1a2e52a58065615ddf88"},
@@ -36,6 +39,7 @@
3639
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
3740
"mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"},
3841
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
42+
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
3943
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
4044
"open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"},
4145
"phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},

0 commit comments

Comments
 (0)