improvement: move docs out of priv, update spark

This commit is contained in:
Zach Daniel 2022-08-19 13:13:27 -04:00
parent c2a69b93ba
commit d7a9ff7996
40 changed files with 6 additions and 767 deletions

View file

@ -280,8 +280,7 @@ defmodule Ash.Actions.Create do
changeset =
Ash.Changeset.require_values(
changeset,
:create,
true
:create
)
|> Ash.Changeset.require_values(
:update,

View file

@ -4,7 +4,6 @@ defmodule Ash.DocIndex do
"""
use Spark.DocIndex,
otp_app: :ash,
guides_from: [
"documentation/**/*.md"
]
@ -13,23 +12,6 @@ defmodule Ash.DocIndex do
@spec for_library() :: String.t()
def for_library, do: "ash"
@overview Spark.DocIndex.read!(:ash, "documentation/topics/overview.md")
@impl true
def guides do
guides = Enum.reject(super(), &(&1.name == "Overview"))
[
%{
name: "Overview",
category: "Topics",
text: @overview,
route: "topics/overview.md"
}
| guides
]
end
@impl true
def default_guide, do: "Overview"

View file

@ -30,7 +30,7 @@ defmodule Ash.MixProject do
end
defp extras() do
"priv/documentation/**/*.md"
"documentation/**/*.md"
|> Path.wildcard()
|> Enum.map(fn path ->
title =
@ -55,7 +55,7 @@ defmodule Ash.MixProject do
end
defp groups_for_extras() do
"priv/documentation/*"
"documentation/*"
|> Path.wildcard()
|> Enum.map(fn folder ->
name =
@ -153,6 +153,8 @@ defmodule Ash.MixProject do
[
name: :ash,
licenses: ["MIT"],
files: ~w(lib .formatter.exs mix.exs README* LICENSE*
CHANGELOG* documentation),
links: %{
GitHub: "https://github.com/ash-project/ash"
}

View file

@ -36,7 +36,7 @@
"providers": {:hex, :providers, "1.8.1", "70b4197869514344a8a60e2b2a4ef41ca03def43cfb1712ecf076a0f3c62f083", [:rebar3], [{:getopt, "1.0.1", [hex: :getopt, repo: "hexpm", optional: false]}], "hexpm", "e45745ade9c476a9a469ea0840e418ab19360dc44f01a233304e118a44486ba0"},
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
"sourceror": {:hex, :sourceror, "0.11.2", "549ce48be666421ac60cfb7f59c8752e0d393baa0b14d06271d3f6a8c1b027ab", [:mix], [], "hexpm", "9ab659118896a36be6eec68ff7b0674cba372fc8e210b1e9dc8cf2b55bb70dfb"},
"spark": {:hex, :spark, "0.1.11", "7415e7775d4cc9ed898bf082e60cfa2bdebd71a690c09b5f92b6f8f05d6be992", [:mix], [{:libgraph, "~> 0.13.3", [hex: :libgraph, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "1c4df53d53f952a8f6cb7876b6499032870f0eceb8ef55e48aa0b3bc61a5eba0"},
"spark": {:hex, :spark, "0.1.15", "299c7b9a8d1d2c994940ff851370892e8484135abec47ab7ae71c4a731e759b4", [:mix], [{:libgraph, "~> 0.13.3", [hex: :libgraph, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "529ec5946186e3976de75ec979fc8b8fb079af2a927c624214cb1f0dd4653118"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},

View file

@ -1,396 +0,0 @@
# Getting Started Tutorial
This tutorial will walk you through creating a very simple application that uses
Ash. The finished application will look like this:
https://github.com/mario-mazo/my_app
## Creating an application
The first step is to [create an application](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html).
```shell
mix new my_app
```
Note: alternatively you create a phoenix application with `mix phx.new` (which is covered in more detail in the [next guide](getting_started_phx.html)).
## Add Ash
Add `ash` to your dependencies in `mix.exs`. The latest version can be found by running `mix hex.info ash`.
```elixir
# in mix.exs
def deps() do
[
{:ash, "~> x.x.x"}
]
end
```
If you want to have a more idiomatic formatting (like the formatting used in the
documentation) of your Ash resource and APIs, you need to add `:ash` (and any other
extensions you use like `:ash_postgres`) to your `.formatter.exs` otherwise the
default Elixir formatter will wrap portions of the DSL in parenthesis.
```elixir
import_deps: [
:ash # add this line
]
```
Without that, instead of:
```elixir
attribute :id, :integer, allow_nil?: true
```
the Elixir formatter will change it to:
```elixir
attribute(:id, :integer, allow_nil?: true)
```
## Create an Ash API
Create an API module. This will be your primary way to interact with your Ash resources. We recommend `lib/my_app/api.ex` for simple setups.
```elixir
# lib/my_app/api.ex
defmodule MyApp.Api do
use Ash.Api
resources do
end
end
```
## Create a registry
The registry is in charge of keeping track of the resources available to an api.
```elixir
# lib/my_app/registry.ex
defmodule MyApp.Registry do
use Ash.Registry,
extensions: [Ash.Registry.ResourceValidations]
entries do
end
end
```
## Refer to that registry in your api
```elixir
# lib/my_app/api.ex
defmodule MyApp.Api do
use Ash.Api
resources do
registry MyApp.Registry
end
end
```
## Create a resource
A resource is the primary entity in Ash. Your API module ties your resources together and gives them an interface, but the vast majority of your configuration will live in resources.
In your typical setup, you might have a resource per database table. For those already familiar with [Ecto](https://github.com/elixir-ecto/ecto), a resource and an Ecto schema are very similar. In fact, all resources define an Ecto schema under the hood. This can be leveraged when you need to do things that are not yet implemented or fall outside of the scope of Ash. The current recommendation for where to put your resources is in `lib/my_app/resources/<resource_name>.ex`. Here are a few examples:
```elixir
# in lib/my_app/resources/tweet.ex
defmodule MyApp.Tweet do
use Ash.Resource
attributes do
uuid_primary_key :id
attribute :body, :string do
allow_nil? false
constraints max_length: 255
end
# Alternatively, you can use the keyword list syntax
# You can also set functional defaults, via passing in a zero
# argument function or an MFA
attribute :public, :boolean, allow_nil?: false, default: false
# This is set on create
create_timestamp :inserted_at
# This is updated on all updates
update_timestamp :updated_at
# `create_timestamp` above is just shorthand for:
# attribute :inserted_at, :utc_datetime_usec,
# private?: true,
# writable?: false,
# default: &DateTime.utc_now/0
end
end
# in lib/my_app/resources/user.ex
defmodule MyApp.User do
use Ash.Resource
attributes do
attribute :email, :string,
allow_nil?: false,
constraints: [
# Note: This regex is just an example
match: ~r/^[\w.!#$%&*+\-\/=?\^`{|}~]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$/i
]
uuid_primary_key :id
end
end
```
For full details on defining a resource, see: `Ash.Resource.Dsl`.
## Add resources to your API
Alter your Registry (`lib/my_app/registry.ex`) to add the resources we created on the previous step:
```elixir
entries do
entry MyApp.User
entry MyApp.Tweet
end
```
### Test the resources
Now you are able to create changesets for your resources using `Ash.Changeset.new/2`:
```elixir
iex(7)> changeset = Ash.Changeset.new(MyApp.User, %{email: "ash.man@enguento.com"})
#Ash.Changeset<
action_type: :create,
attributes: %{email: "ash.man@enguento.com"},
relationships: %{},
errors: [],
data: %MyApp.User{
__meta__: #Ecto.Schema.Metadata<:built, "">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: nil,
id: nil
},
valid?: true
>
```
If you try to use an invalid email (the email regex is for demonstration purposes only)
an error will be returned:
```elixir
iex(6)> changeset = Ash.Changeset.new(MyApp.User, %{email: "@eng.com"})
#Ash.Changeset<
action_type: :create,
attributes: %{},
relationships: %{},
errors: [
%Ash.Error.Changes.InvalidAttribute{
class: :invalid,
field: :email,
message: {"must match the pattern %{regex}",
[
regex: "~r/^[\\w.!#$%&’*+\\-\\/=?\\^`{|}~]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*$/i"
]},
path: [],
stacktrace: #Stacktrace<>
}
],
data: %MyApp.User{
__meta__: #Ecto.Schema.Metadata<:built, "">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: nil,
id: nil
},
valid?: false
>
```
## Add your data layer
To be able to store and later on read your resources, a _data layer_ is required. For more information, see the documentation for the data layer you would like to use. The currently supported data layers are listed below:
| Storage | Datalayer | Storage Documentation |
| -------- | -------------------------------------------------------- | ------------------------------------------------------------------------ |
| postgres | [AshPostgres.DataLayer](https://hexdocs.pm/ash_postgres) | [Postgres Documentation](https://www.postgresql.org/docs/) |
| csv | [AshCsv.DataLayer](https://hexdocs.pm/ash_csv) | [CSV Information](https://en.wikipedia.org/wiki/Comma-separated_values) |
| ets | `Ash.DataLayer.Ets` | [Erlang Term Storage Documentation](https://erlang.org/doc/man/ets.html) |
| mnesia | `Ash.DataLayer.Mnesia` | [Mnesia Documentation](https://erlang.org/doc/man/mnesia.html) |
To add a data layer, we need to add it to the `use Ash.Resource` statement. In
this case we are going to use ETS which is a in-memory data layer that is built
into the BEAM and works well for testing purposes.
```elixir
# in both lib/my_app/resources/user.ex
# and lib/my_app/resources/tweet.ex
use Ash.Resource, data_layer: Ash.DataLayer.Ets
```
## Add actions to enable functionality
Actions are the primary driver for adding specific interactions to your resource.
You can read the about `Ash.Resource.Dsl` [actions](Ash.Resource.Dsl.html#module-actions)
to learn how to customize the functionality. For now we will enable all of them with default implementations by adding the following block to your resources:
```elixir
# in both lib/my_app/resources/user.ex
# and lib/my_app/resources/tweet.ex
actions do
create :create
read :read
update :update
destroy :destroy
end
```
### Test functionality
Now you should be able to use your API to do CRUD operations on your resources.
#### Create resource
```elixir
iex(1)> user_changeset = Ash.Changeset.new(MyApp.User, %{email: "ash.man@enguento.co
m"})
#Ash.Changeset<
action_type: :create,
attributes: %{email: "ash.man@enguento.com"},
relationships: %{},
errors: [],
data: %MyApp.User{
__meta__: #Ecto.Schema.Metadata<:built, "">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: nil,
id: nil
},
valid?: true
>
iex(2)> MyApp.Api.create(user_changeset)
{:ok,
%MyApp.User{
__meta__: #Ecto.Schema.Metadata<:built, "">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: "ash.man@enguento.com",
id: "2642ca11-330b-4a07-83c7-b0e9ef391df6"
}}
```
##### List and Read a resource
```elixir
iex(3)> MyApp.Api.read(MyApp.User)
{:ok,
[
%MyApp.User{
__meta__: #Ecto.Schema.Metadata<:built, "">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: "ash.man@enguento.com",
id: "2642ca11-330b-4a07-83c7-b0e9ef391df6"
}
]}
iex(4)> MyApp.Api.get(MyApp.User, "2642ca11-330b-4a07-83c7-b0e9ef391df6")
{:ok,
%MyApp.User{
__meta__: #Ecto.Schema.Metadata<:built, "">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: "ash.man@enguento.com",
id: "2642ca11-330b-4a07-83c7-b0e9ef391df6"
}}
```
## Add relationships
With our resources stored in a data layer we can move on
to create relationships between them. In this case we will
specify that a `User` can have many `Tweets` - this implies that
a `Tweet` belongs to a specific `User`.
```elixir
# in lib/my_app/resources/user.ex
relationships do
has_many :tweets, MyApp.Tweet, destination_attribute: :user_id
end
# in lib/my_app/resources/tweet.ex
relationships do
belongs_to :user, MyApp.User
end
```
### Test relationships
Now we can use the new relationship to create a `Tweet` that belongs to a specific `User`:
```elixir
iex(8)> {:ok, user} = Ash.Changeset.new(MyApp.User, %{email: "ash.man@enguento.com"}) |> MyApp.Api.create()
{:ok,
%MyApp.User{
__meta__: #Ecto.Schema.Metadata<:built, "">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: "ash.man@enguento.com",
id: "0d7063f8-b07c-4d02-88b2-b671f1aa0ad9",
tweets: #Ash.NotLoaded<:relationship>
}}
iex(9)> MyApp.Tweet |> Ash.Changeset.new(%{body: "ashy slashy"}) |> Ash.Changeset.replace_relationship(:user, user) |> MyApp.Api.create()
{:ok,
%MyApp.Tweet{
__meta__: #Ecto.Schema.Metadata<:built, "">,
__metadata__: %{},
aggregates: %{},
body: "ashy slashy",
calculations: %{},
inserted_at: ~U[2020-11-14 12:54:06Z],
id: "f0b0b9d5-832c-45c9-9313-5e3fb9f1af24",
public: false,
updated_at: ~U[2020-11-14 12:54:06Z],
user: %MyApp.User{
__meta__: #Ecto.Schema.Metadata<:built, "">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: "ash.man@enguento.com",
id: "0d7063f8-b07c-4d02-88b2-b671f1aa0ad9",
tweets: #Ash.NotLoaded<:relationship>
},
user_id: "0d7063f8-b07c-4d02-88b2-b671f1aa0ad9"
}}
```
## Add a Phoenix Frontend
Now that the Elixir API is complete, you can move on to the [next
guide](getting_started_phx.html) to learn how to change the data_layer to
PostgreSQL and expose it via a JSON API.
- `AshJsonApi` - can be used to build a spec compliant JSON:API.
- `AshPostgres.DataLayer` - can be used to persist your resources to PostgreSQL.
## See Ash documentation for the rest
- `Ash.Api` for what you can do with your resources.
- `Ash.Query` for the kinds of queries you can make.
- `Ash.Resource.Dsl` for the resource DSL documentation.
- `Ash.Api.Dsl` for the API DSL documentation.

View file

@ -1,348 +0,0 @@
# Getting started with Ash and Phoenix
In this guide we will convert the sample app from the [getting
stated guide](getting_started.html) into
a full blown service backed by PostgreSQL as a storage and a Json Web API.
For the web part of the application we will rely on the
[Phoenix framework](https://www.phoenixframework.org/) as both frameworks are complementary.
Keep in mind that using Phoenix is not a requirement, you could
alternatively use [Plug](https://github.com/elixir-plug/plug).
You can check out the completed application and source code in this [repo](https://github.com/mario-mazo/my_app_phx).
## Create Phoenix app
We create a simple Phoenix application and we remove some unnecessary parts,
also we are using `--app` to rename the application so it matches the name from
the getting started guide.
```shell
mix phx.new my_app --no-html --no-webpack --no-gettext
```
## Add dependencies and formatter
Now we need to add the dependencies, `ash` and [ash_postgres](https://hexdocs.pm/ash_postgres/readme.html). To find out what the latest available version is you can use `mix hex.info`:
```shell
mix hex.info ash_postgres
mix hex.info ash
```
Next modify the the `.formatter` and `mix.exs` files:
```diff
--- a/.formatter.exs
+++ b/.formatter.exs
@@ -1,4 +1,13 @@
[
+ import_deps: [
+ :ash_json_api,
+ :ash_postgres
+ ],
--- b/mix.exs
+++ b/mix.exs
@@ -33,6 +33,8 @@ defmodule MyAppPhx.MixProject do
# Type `mix help deps` for examples and options.
defp deps do
[
+ {:ash_postgres, "~> 0.25.5"},
+ {:ash, "~> 1.24"}
```
Next, modify `MyApp.Repo` to use `AshPostgres.Repo` instead of `Ecto.Repo`, and add the `uuid-ossp` extension (unless you won't be using uuids).
```elixir
defmodule MyApp.Repo do
use AshPostgres.Repo,
otp_app: :my_app
def installed_extensions do
["uuid-ossp"]
end
end
```
Make sure you can connect to Postgres by verifying that the credentials in `config/dev.exs` are correct and create the database by running:
```shell
mix ecto.create
* The database for MyApp.Repo has been created
```
To configure Phoenix to support the [jsonapi](https://jsonapi.org/) content type, add the following configuration to `config/config.exs`:
```diff
--- a/config/config.exs
+++ b/config/config.exs
@@ -10,6 +10,10 @@ use Mix.Config
config :my_app,
ecto_repos: [MyApp.Repo]
+config :mime, :types, %{
+ "application/vnd.api+json" => ["json"]
+}
+
```
### Reuse the files from the Getting Started guide
Copy the `lib/my_app/api.ex`, `lib/my_app/resources/tweet.ex`
and `lib/my_app/resources/user.ex` from the Getting Started
sample app into this project in the same path.
## Switch data layer to Postgres
We can now proceed to switch the data layer from `ETS`
to `PostgreSQL` simply by changing the `data_layer` to
`AshPostgres.DataLayer` in our resources
and adding the table name and our repo. In this case we will
use the default repo created by Phoenix.
```diff
--- a/my_app_phx/lib/my_app/resources/tweet.ex
+++ b/my_app_phx/lib/my_app/resources/tweet.ex
@@ -1,6 +1,11 @@
# in my_app_phx/lib/my_app/resources/tweet.ex
defmodule MyApp.Tweet do
- use Ash.Resource, data_layer: Ash.DataLayer.Ets
+ use Ash.Resource, data_layer: AshPostgres.DataLayer
+
+ postgres do
+ table "tweets"
+ repo MyApp.Repo
+ end
--- a/my_app_phx/lib/my_app/resources/user.ex
+++ b/my_app_phx/lib/my_app/resources/user.ex
@@ -1,6 +1,11 @@
# in my_app_phx/lib/my_app/resources/user.ex
defmodule MyApp.User do
- use Ash.Resource, data_layer: Ash.DataLayer.Ets
+ use Ash.Resource, data_layer: AshPostgres.DataLayer
+
+ postgres do
+ table "users"
+ repo MyApp.Repo
+ end
```
Now you can tell Ash to generate the migrations from your API:
```shell
mix ash_postgres.generate_migrations --apis MyApp.Api
* creating priv/repo/migrations/20201120214857_migrate_resources1.exs
```
and run the ecto migration to generate the tables:
```shell
run mix ecto.migrate
23:23:46.067 [info] == Running 20201120222312 MyApp.Repo.Migrations.MigrateResources1.up/0 forward
23:23:46.070 [info] create table users
23:23:46.076 [info] create table tweets
23:23:46.090 [info] == Migrated 20201120222312 in 0.0s
```
### Test PostgreSQL integration
Start IEx with `iex -S mix phx.server` and lets run the same test
we ran in the initial `my_app`. You will now see that SQL statements
are being executed and data is now stored in your PostgreSQL database.
```elixir
iex(1)> {:ok, user} = Ash.Changeset.new(MyApp.User, %{email: "ash.man@enguento.com"}) |> MyApp.Api.create()
[debug] QUERY OK db=1.2ms idle=1432.0ms
begin []
[debug] QUERY OK db=0.4ms
INSERT INTO "users" ("email","id") VALUES ($1,$2) ["ash.man@enguento.com", <<72, 22
6, 94, 187, 145, 81, 66, 25, 183, 79, 59, 199, 93, 88, 32, 243>>]
[debug] QUERY OK db=0.3ms
commit []
{:ok,
%MyApp.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: "ash.man@enguento.com",
id: "48e25ebb-9151-4219-b74f-3bc75d5820f3",
tweets: #Ash.NotLoaded<:relationship>
}}
iex(2)> MyApp.Tweet |> Ash.Changeset.new(%{body: "ashy slashy"}) |> Ash.Changeset.r
eplace_relationship(:user, user) |> MyApp.Api.create()
[debug] QUERY OK db=0.1ms idle=1197.5ms
begin []
[debug] QUERY OK db=2.2ms
INSERT INTO "tweets" ("body","inserted_at","id","public","updated_at","user_id") VAL
UES ($1,$2,$3,$4,$5,$6) ["ashy slashy", ~U[2020-11-22 21:15:33Z], <<163, 22, 225, 4
3, 217, 10, 67, 242, 152, 149, 197, 133, 253, 154, 244, 95>>, false, ~U[2020-11-22
21:15:33Z], <<72, 226, 94, 187, 145, 81, 66, 25, 183, 79, 59, 199, 93, 88, 32, 243>
>]
[debug] QUERY OK db=0.3ms
commit []
{:ok,
%MyApp.Tweet{
__meta__: #Ecto.Schema.Metadata<:loaded, "tweets">,
__metadata__: %{},
aggregates: %{},
body: "ashy slashy",
calculations: %{},
inserted_at: ~U[2020-11-22 21:15:33Z],
id: "a316e12b-d90a-43f2-9895-c585fd9af45f",
public: false,
updated_at: ~U[2020-11-22 21:15:33Z],
user: %MyApp.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
__metadata__: %{},
aggregates: %{},
calculations: %{},
email: "ash.man@enguento.com",
id: "48e25ebb-9151-4219-b74f-3bc75d5820f3",
tweets: #Ash.NotLoaded<:relationship>
},
user_id: "48e25ebb-9151-4219-b74f-3bc75d5820f3"
}}
```
### Exposing the API with a JSON API
First we need to add the extension dependency for [ash_json_api](https://hexdocs.pm/ash_json_api/readme.html).
```shell
mix hex.info ash_json_api
```
Add it to your dependencies and don't forget to run `mix deps.get`:
```diff
--- a/mix.exs
+++ b/mix.exs
@@ -33,6 +33,7 @@ defmodule MyApp.MixProject do
# Type `mix help deps` for examples and options.
defp deps do
[
+ {:ash_json_api, "~> 0.24.1"},
{:ash_postgres, "~> 0.25.5"},
{:ash, "~> 1.24"},
{:phoenix, "~> 1.5.6"},
```
Create a router module for your Api
```elixir
defmodule MyApp.MyApi.Router do
# The registry must be explicitly provided here
use AshJsonApi.Api.Router, api: Api, registry: Registry
end
```
We can proceed to add a route in the Phoenix router to forward requests
to our Ash API. To do so we use `AshJsonApi.forward/3` as shown in
`lib/my_app_web/router.ex`:
```diff
--- a/lib/my_app_web/router.ex
+++ b/lib/my_app_web/router.ex
@@ -1,12 +1,14 @@
defmodule MyAppWeb.Router do
- scope "/api", MyAppWeb do
+ scope "/api" do
pipe_through :api
+ forward("/", MyApp.MyApi.Router)
end
```
After that, all we have to do is configure our resources for the JSON:API.
In this guide we will only expose an API for the `user` resource, exposing the `tweet` resource is left as an exercise for the reader.
We need to add the extension to our resource and define a mapping between
the REST verbs and our internal API actions.
```diff
--- a/lib/my_app/resources/user.ex
+++ b/lib/my_app/resources/user.ex
@@ -1,7 +1,24 @@
# in lib/my_app/resources/user.ex
defmodule MyApp.User do
- use Ash.Resource, data_layer: AshPostgres.DataLayer
+ use Ash.Resource, data_layer: AshPostgres.DataLayer,
+ extensions: [
+ AshJsonApi.Resource
+ ]
+ json_api do
+ type "user"
+
+ routes do
+ base "/users"
+
+ get :read
+ index :read
+ post :create
+ patch :update
+ delete :destroy
+ end
+ end
+
```
### Test Web Json API
Fire up IEx with `iex -S mix phx.server` and curl the API:
```shell
curl -s --request GET --url 'http://localhost:4000/api/users' | jq
{
"data": [
{
"attributes": {
"email": "ash.man@enguento.com"
},
"id": "46b60ec8-5b0f-461d-95ab-bcc5169ff831",
"links": {},
"meta": {},
"relationships": {},
"type": "user"
},
{
"attributes": {
"email": "ash.man@enguento.com"
},
"id": "cd84148a-4af4-4f9f-952f-9daa28946e01",
"links": {},
"meta": {},
"relationships": {},
"type": "user"
},
{
"attributes": {
"email": "ash.man@enguento.com"
},
"id": "48e25ebb-9151-4219-b74f-3bc75d5820f3",
"links": {},
"meta": {},
"relationships": {},
"type": "user"
}
],
"jsonapi": {
"version": "1.0"
},
"links": {
"self": "http://localhost:4000/api/users"
}
}
```