{"id":20730997,"url":"https://github.com/tyalt1/petal_stack_tutorial","last_synced_at":"2025-03-11T10:19:54.060Z","repository":{"id":242424349,"uuid":"809472545","full_name":"tyalt1/petal_stack_tutorial","owner":"tyalt1","description":"Teaching examples for PETAL stack (Phoenix, Elixir, Tailwind, Ash, LiveView)","archived":false,"fork":false,"pushed_at":"2024-06-05T19:43:26.000Z","size":150,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-18T00:26:13.927Z","etag":null,"topics":["elixir","elixir-examples","elixir-lang","elixir-language","elixir-phoenix","elixir-phoenix-framework","elixir-programming-language","liveview","phoenix","tutorial","tutorials"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tyalt1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-02T19:31:30.000Z","updated_at":"2024-10-31T20:51:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"71a59627-5ebe-4b7e-8f0e-4538eef78d96","html_url":"https://github.com/tyalt1/petal_stack_tutorial","commit_stats":null,"previous_names":["tyalt1/petal_stack_tutorial"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyalt1%2Fpetal_stack_tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyalt1%2Fpetal_stack_tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyalt1%2Fpetal_stack_tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyalt1%2Fpetal_stack_tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tyalt1","download_url":"https://codeload.github.com/tyalt1/petal_stack_tutorial/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243012703,"owners_count":20221606,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","elixir-examples","elixir-lang","elixir-language","elixir-phoenix","elixir-phoenix-framework","elixir-programming-language","liveview","phoenix","tutorial","tutorials"],"created_at":"2024-11-17T05:13:24.900Z","updated_at":"2025-03-11T10:19:54.048Z","avatar_url":"https://github.com/tyalt1.png","language":"Elixir","readme":"# Petal Stack Tutorial\n\nThis is tutorial repo for the learning the PETAL stack.\n\nDisclaimer: This stack does not use Alpine.js. The A in the PETAL stack usually refers to Alpine.js however Alpine.js is typically no longer used in Phoenix applications because of additional functionallity added to LiveView, specifically in the `Phoenix.LiveView.JS` module. This stack adds the use of the Ash framework, as it helps declare models quickly and derive functionality with minimal code. And because it starts with an A.\n\n## PETAL Stack\n\nLetter | Name/Link | Description\n------|------|------\nP | [Phoenix](https://www.phoenixframework.org/) | Server-sided web framework built in Elixir.\nE | [Elixir](https://hexdocs.pm/elixir/introduction.html) | Functional, concurrent, high-level programming language that runs on the Erlang (BEAM) virtual machine.\nT | [Tailwind](https://tailwindcss.com/) | Utility-first CSS framework.\nA | [Ash](https://ash-hq.org/) | Declarative model framework for Elixir.\nL | [LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html) | Library for creating UIs in declarative HTML and updated via websocket events.\n\n## Why the PETAL stack?\n\nThe PETAL stack is scalable and productive.\n\n### Examples of scalability\n\nElixir runs on the Erlang VM which has many examples of scalability\n\n- The company that created Erlang boasts only 5.2 minutes of downtime a year in their systems running Erlang.\n- Whatsup was able to maintain 2 million TCP connections on a single server using 40% CPU of a server from 2012 using Erlang. [Source](https://blog.whatsapp.com/1-million-is-so-2011)\n- A developer rewrote an AWS microservice application in Elixir. The resulting application was faster and cost less to run in the cloud. [Source](https://medium.com/coryodaniel/from-erverless-to-elixir-48752db4d7bc)\n- Discord has many examples of using Elixir as thier primay language to scale to a large amount of users. [Source](https://discord.com/blog/using-rust-to-scale-elixir-for-11-million-concurrent-users)\n\n### Examples of productivity\n\n- The only dependancy is Elixir. Phoenix is built on Elixir, Phoenix comes with a utility to run Tailwind by default, and the other parts of the stack are Elixir packages.\n- Elixir features a macro system so that users can do more with less code.\n- Phoenix is a batteries included framework that's designed to get started as fast as possible.\n  - [Source: Build a real-time twitter clone in 15 minutes](https://www.phoenixframework.org/blog/build-a-real-time-twitter-clone-in-15-minutes-with-live-view-and-phoenix-1-5)\n- Including Tailwind means your project starts with over 37,000 CSS classes already defined.\n- LiveView handles the connection to between backend and frontend so you don't need to maintain a backend REST API or a Javascript client for your frontend.\n- With LiveView developers can write reactive single-page frontends in HTML and Elixir. (No Javascript)\n- Ash lets you define a model and then with minimal code derive database persistance, code wrappers, REST API, GraphQL API, and more.\n\n## PETAL Stack Q/A\n\n### Q: Do I have to use Ash?\n\nNo. 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:\n\n- Resource and Domain level functions\n- Database persistance (Postgres, SQLite3)\n- alternative persistance (in memory, ETS table, Mnesia table, CSV file)\n- Form validation\n- JSON API mapping\n- GraphQL API mapping\n- and more with a robust extension system ...\n\n## Commit References\n\nThis is a summary of notable commits, and sections that go into that code in greater detail.\n\n Section # | Commit | Description\n------|------|------\n 1 | [`12eb0e4`](https://github.com/tyalt1/petal_stack_tutorial/commit/12eb0e4dea49628c4dcfa6df3e7c6b6b4c622715) | All files generated by running `mix phx.new petal_stack_tutorial`\n 2 | [`d2a1733`](https://github.com/tyalt1/petal_stack_tutorial/commit/d2a17336ceb58cb769fdd052cf838e73a344b453) | LiveView Example\n 3 | [`0658c29`](https://github.com/tyalt1/petal_stack_tutorial/commit/0658c2911d7ca8b52ebeabcc8e930d4336edbdf7) | Ash.Resource Example\n 4 | [`da74838`](https://github.com/tyalt1/petal_stack_tutorial/commit/da74838c458e23868d344bcfce907bc964eb5d22) | AshAuthentication Example\n 5 | [`1d0338f`](https://github.com/tyalt1/petal_stack_tutorial/commit/1d0338f287dcdbb68104174f895400acc9a33f32) | AshJsonApi Example\n 6 | [`5ca4a08`](https://github.com/tyalt1/petal_stack_tutorial/commit/5ca4a088e00289b6cb8334b04f3244a55e3c4fd2) | AshGraphql Example\n\n## Tutorial\n\n### Table of Contents\n\n1. [Setup Elixir and Phoenix](#section-1-setup-elixir-and-phoenix)\n2. [Intro to LiveView](#section-2-intro-to-liveview)\n3. [Intro to Ash](#section-3-intro-to-ash)\n4. [Users with Ash Authentication](#section-4-users-with-ash-authentication)\n5. [REST API with with Ash JSON API](#section-5-rest-api-with-ash-json-api)\n6. [GraphQL API with Ash GraphQL](#section-6-graphql-api-with-ash-graphql)\n\n### Section 1: Setup Elixir and Phoenix\n\nInstall Elixir\n```bash\n# install asdf (a tool for managing runtimes)\n# Getting started: https://asdf-vm.com/guide/getting-started.html\n\n# install erlang and elixir plugins\nasdf plugin add erlang\nasdf plugin add elixir\n\n# install specific version for plugin\nasdf install $PLUGIN $VERSION\n\n# or if you have a .tool-versions file\n# install from .tool-versions file\nasdf install\n```\n\nCreate a new Phoenix Project\n```bash\n# install Hex (Elixir package manager, like npm or pip)\nmix local.hex\n\n# update hex and install Phoenix creation script\nmix archive.install hex phx_new\n\n# create a new phoenix app\nmix phx.new my_app\n\n# set elixir versions, this will create a .tool-versions file\nasdf local erlang 26.2.4\nasdf local elixir 1.16.2-otp-26\n```\n\nRun the server\n```bash\n# run Postgres via Docker\ndocker run -d \\\n  --name phx_db \\\n  -e POSTGRES_USER=postgres \\\n  -e POSTGRES_PASSWORD=postgres \\\n  -e POSTGRES_HOST_AUTH_METHOD=trust \\\n  -p 5432:5432 \\\n  postgres\n\n# install dependencies, run migrations\nmix setup\n\n# run the server\nmix phx.server\n\n# run the server and a console\niex -S mix phx.server\n```\n\n### Section 2: Intro to LiveView\n\nWe're going to implement a simple counter.\n\nIn newer version of Pheonix, LiveView comes included so there is no new dependency needed.\n\nThe file `lib/petal_stack_tutorial_web/router.ex` contains the routing logic to associate controllers with verb-path pairs. Add the following line to add a new route that will point to our live view.\n\n```elixir\nlive \"/counter\", Counter\n```\n\nCreate a new file `lib/petal_stack_tutorial_web/live/counter.ex` with the following content\n```elixir\ndefmodule PetalStackTutorialWeb.Counter do\n  use PetalStackTutorialWeb, :live_view\n\n  # more code here\nend\n```\n\nNote: `use PetalStackTutorialWeb, :live_view` inserts `use Phoenix.LiveView` which makes this a live view.\n\nWe need to implement minimum 3 callbacks.\n- `mount/3` which is called when the websocket is mounted, and returns the inital state.\n- `render/1` which takes the state and returns HEEx (HTML Embedded Elixir) template.\n- `handle_update/3` which handles events and updates the state.\n\nImplment the `mount/3` function.\n```elixir\n@impl true\ndef mount(_params, _session, socket) do\n  {:ok, assign(socket, counter: 0)}\nend\n```\n\nNote: Add the `@impl true` to explicitly say you're implementing a callback.\n\n`mount/3` must return a tuple with `:ok` being first and the socket object being the second. Inside the socket object is a hashmap called `assigns` which we can add state to. The helper function `assign` takes a socket and a key-value and returns a socket object with the updated state. There is other information about the websocket connection in the socket object but we will only care about our own custom state for this exercise.\n\nImplment the `render/1` function.\n```elixir\nattr :click, :string, required: true\nattr :debounce, :integer, default: 20\nslot :inner_block\n\ndefp my_button(assigns) do\n  ~H\"\"\"\n  \u003cbutton phx-click={@click} phx-debounce={@debounce}\u003e\n    \u003c%= render_slot(@inner_block) %\u003e\n  \u003c/button\u003e\n  \"\"\"\nend\n\nattr :counter, :integer, required: true\n\n@impl true\ndef render(assigns) do\n  ~H\"\"\"\n  \u003cdiv\u003e\n    \u003cspan\u003e\u003c%= @counter %\u003e\u003c/span\u003e\n    \u003c.my_button click=\"inc\"\u003e+\u003c/.my_button\u003e\n    \u003c.my_button click=\"dec\"\u003e-\u003c/.my_button\u003e\n  \u003c/div\u003e\n  \"\"\"\nend\n```\n\nNote: The `~H` sigil is a macro that turns the string into a HEEx template.\n\nNote: The `@` is a macro within a HEEx template that accesses the assigns map. `@counter` is equivalent to `assigns[:counter]`.\n\nNote: When displaying a Elixir value in a HEEx template, use `\u003c%= ... %\u003e` for values in HTML and `{ ... }` for values as attributes.\n\nWe implement the `render/1` function that take the assigns map in the socket object. It returns an HEEx template. The template consists of a span to display the value of the counter, and two buttons to increment and decrement the counter. The two buttons have some similar functionality so we can implment another function that is private and returns the button. The `attr` and `slot` macros are helpers for declaring values in the assigns map. A `attr` can be a value that is required or have a default value. You will get a compiler error if a required `attr` is not given. A `slot` is nested HTML.\n\nAfter we implmented this function, we can start the server and open http://localhost:4000/counter in out browser.\n\nWe see the following logs.\n```\n[info] GET /counter\n[debug] Processing with PetalStackTutorialWeb.Counter.Elixir.PetalStackTutorialWeb.Counter/2\n  Parameters: %{}\n  Pipelines: [:browser]\n[info] Sent 200 in 28ms\n[info] CONNECTED TO Phoenix.LiveView.Socket in 12µs\n  Transport: :websocket\n  Serializer: Phoenix.Socket.V2.JSONSerializer\n  Parameters: %{\"_csrf_token\" =\u003e \"Bwg5ATcfGAQMIHJEPC5OGzYDIUYwcHlXcypQuqhSgj6teC7IpuU7fEL3\", \"_live_referer\" =\u003e \"undefined\", \"_mounts\" =\u003e \"0\", \"_track_static\" =\u003e %{\"0\" =\u003e \"http://localhost:4000/assets/app.css\", \"1\" =\u003e \"http://localhost:4000/assets/app.js\"}, \"vsn\" =\u003e \"2.0.0\"}\n[debug] MOUNT PetalStackTutorialWeb.Counter\n  Parameters: %{}\n  Session: %{\"_csrf_token\" =\u003e \"dqIPBnpWkJD0YmyRFvtqV55d\"}\n[debug] Replied in 87µs\n```\n\nHere we see the following\n1. The browser makes a `GET` to `/counter`\n2. The router routes to the `Counter` live view.\n3. A response is made in 28ms, which sends the LiveView response.\n4. That response sends another request to establish a websocket connection.\n5. Connect in 12µs\n6. The `mount/3` callback is called to initialize the state.\n7. The `render/1` callback is called rendering the HTML. This reply is sent in 87µs.\n\nThe web page displays \"0+-\"\n\nRecall we set the `phx-click` and `phx-debounce` attributes on the buttons, which are specific Phoenix LiveView attributes. `phx-click` for the + button is set to the string \"inc\", which means when that element is clicked that string will be sent via the websocket as an event. The `phx-debounce` attribute is set to 20, which is how many milliseconds all events for this element are debounced for. When we click the + we see the following error.\n\n```\n[error] GenServer #PID\u003c0.804.0\u003e terminating\n** (UndefinedFunctionError) function PetalStackTutorialWeb.Counter.handle_event/3 is undefined or private\n```\n\nWhat happens when we click the +\n1. The \"inc\" event is sent via the websocket.\n2. We try to call `handle_event/3` and get a function not defined error.\n3. The Elixir process dies due to a unhandled exception.\n4. The process is restarted by a supervisor and reconnects to our websocket.\n\nYou will see a small red flash alert telling you the page loses connection for a moment, then disappear when the connection is reestablished.\n\nLet's implment `handle_event/3`\n```elixir\n@impl true\ndef handle_event(\"inc\", _params, socket) do\n  {:noreply, update(socket, :counter, fn x -\u003e x+1 end)}\nend\n\n@impl true\ndef handle_event(\"dec\", _params, socket) do\n  f = fn\n    x when x \u003c= 0 -\u003e 0\n    x -\u003e x - 1\n  end\n  {:noreply, update(socket, :counter, f)}\nend\n```\n\nWe use pattern matching to implment the function across 2 seperate clauses. Here if we were to emit an event besides \"inc\" or \"dec\" from our frontend we'd get a function undefined exception. Optionally we could add a thrid catch-all clause to log the result. In both callbacks we use the `update` helper function to take the socket, key of value we want to change, and a function that will transform the value. We use pattern matching to implment the callback so that it can't decrement below 0.\n\nNow when we click the + button we see the log and the counter update.\n```\n[debug] HANDLE EVENT \"inc\" in PetalStackTutorialWeb.Counter\n  Parameters: %{\"value\" =\u003e \"\"}\n[debug] Replied in 413µs\n```\n\nThat is mostly everything you need to get started with LiveView but not all the functionality. Below are notable examples:\n\n- [`Phoenix.LiveComponent`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html) which are components that maintain their own state, handle their own events, and cant be embedded in a LiveView.\n- Handle events locally with [`Phoenix.LiveView.JS`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html)\n- [Forms](https://hexdocs.pm/phoenix_live_view/form-bindings.html)\n- Implement `handle_info/2` to handle server-side events sent from other processes, for real-time updates to the UI.\n- [File uploads](https://hexdocs.pm/phoenix_live_view/uploads.html)\n- Lazy-Evaluation style [streaming values](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream/4)\n\n### Section 3: Intro to Ash\n\nAsh is used to declare models and derive other functionality from those declarations. A declaration is an `Ash.Resource`. All `Ash.Resource`s are inside an `Ash.Domain`.\n\nWe'll follow the [Ash Phoenix tutorial](https://ash-hq.org/docs/guides/ash_phoenix/latest/tutorials/getting-started-with-ash-and-phoenix) for creating a Blog.\n\n#### Ash Boilerplate\n\nAsh is the only element of the PETAL Stack that does not come with Elixir or Phoenix. Install Ash by adding the following and run `mix deps.get`\n```elixir\ndefp deps do\n  [\n    {:ash, \"~\u003e 3.0\"},\n    {:ash_phoenix, \"~\u003e 2.0\"},\n    {:ash_postgres, \"~\u003e 2.0\"},\n    ...\n  ]\nend\n```\n\nNote: `AshPostgres` is built on top of `Ecto`, a database library for Elixir. `Ecto` is what Phoenix uses by default for communicating with the database.\n\nOptionally, make the changes to `.formatter.exs`\n```elixir\n[\n  import_deps: [..., :ash, :ash_phoenix, :ash_postgres],\n  ...\n]\n```\n\nChange the file `lib/petal_stack_tutorial/repo.ex` to contain this.\n```elixir\ndefmodule PetalStackTutorial.Repo do\n  use AshPostgres.Repo,\n    otp_app: :petal_stack_tutorial\n\n  # Installs extensions that ash commonly uses\n  def installed_extensions do\n    [\"ash-functions\", \"uuid-ossp\", \"citext\"]\n  end\nend\n```\n\nNote: `otp_app` must be set to the application name.\n\nRemove references to `ecto` in your `mix.exs` `aliases`. Replace `ecto.setup` with `ash.setup`. It should look something like this:\n```elixir\ndefp aliases do\n  [\n    setup: [\"deps.get\", \"ash.setup\", \"assets.setup\", \"assets.build\"],\n    ...\n  ]\nend\n```\n\nMake the change to the `config/config.exs` files. We need to list all domains. For now we'll add the Blog domain we're adding next.\n```elixir\nconfig :petal_stack_tutorial,\n  ash_domains: [PetalStackTutorial.Blog]\n```\n\nCreate the file `lib/petal_stack_tutorial/blog/blog.ex` which will be our `Blog` domain. This domain will contain the Post resource we're adding next.\n```elixir\ndefmodule PetalStackTutorial.Blog do\n  use Ash.Domain\n\n  resources do\n    resource PetalStackTutorial.Blog.Post\n  end\nend\n```\n\nNow we can create a Post resource.\n```elixir\ndefmodule PetalStackTutorial.Blog.Post do\n  use Ash.Resource,\n    domain: PetalStackTutorial.Blog,\n    data_layer: AshPostgres.DataLayer,\n    extensions: []\n\n  postgres do\n    table \"posts\"\n    repo PetalStackTutorial.Repo\n  end\n\n  attributes do\n    uuid_primary_key :id\n    create_timestamp :created_at\n    update_timestamp :updated_at\n\n    attribute :title, :string do\n      allow_nil? false\n    end\n\n    attribute :content, :string\n  end\n\n  actions do\n    defaults [:read, :destroy]\n\n    create :create do\n      accept [:title]\n    end\n\n    update :update do\n      accept [:content]\n    end\n\n    read :get do\n      argument :id, :uuid, allow_nil?: false\n      get? true # read will only return 1 value, not a list\n      filter expr(id == ^arg(:id))\n    end\n  end\nend\n```\n\nThe syntax for `Ash.Resource` and `Ash.Domain` should look odd. This is not Elixir, this is a DSL defined using Elixir macros. Refer to the [Ash DSL documentation](https://hexdocs.pm/ash/3.0.9/dsl-ash-resource.html) for more info.\n\n#### Deep dive into `Ash.Resource`\n\nReview of what we've done so far\n1. We added Ash, AshPhoenix, and AshPostgres as dependancies\n2. Added formatting rules for `mix format`\n3. Refactored `PetalStackTutorial.Repo` to use `AshPostgres` instead of `Ecto`.\n4. Added `ash.setup` to our `setup` mix alias.\n5. Add list of domains to our config file, include the Blog domain.\n6. Create the Blog domain, which contains the Post resource.\n7. Created the Post resource.\n\nThe first 4 steps were boiler plate we don't need to touch again. The config file only needs to be updated if we create a new doamin. The domain was made to hold the resource. There isn't much to do in the domain yet. Let's look at the Post resource.\n\n```elixir\nuse Ash.Resource,\n    domain: PetalStackTutorial.Blog,\n    data_layer: AshPostgres.DataLayer,\n    extensions: []\n```\n\nThis declares the module to be a `Ash.Resource`. `domain` is the domain module. We've already defined the Blog module. `data_layer` is the module that defines how this module will be persisted. Ash comes with data layers that saves these models in memory, in ETS, and more. The `AshPostgres.DataLayer` is a data layer we installed that writes the value to a Postgres database. `extensions` can be used to add more blocks than what Ash comes with. These can be used to add the `json_api` block for generating REST APIs or the `graphql` block for generating GraphQL APIs. For now we will have no extensions.\n\n```elixir\npostgres do\n  table \"posts\"\n  repo PetalStackTutorial.Repo\nend\n```\n\nThis block declares the name of the table and what `Repo` module is used to read/write to/from the database.\n\n```elixir\nattributes do\n  uuid_primary_key :id\n  create_timestamp :created_at\n  update_timestamp :updated_at\n\n  attribute :title, :string do\n    allow_nil? false\n  end\n\n  attribute :content, :string\nend\n\nactions do\n  defaults [:read, :destroy]\n\n  create :create do\n    accept [:title, :content]\n  end\n\n  update :update do\n    accept [:content] # only edit content, not title\n  end\n\n  read :get do\n    argument :id, :uuid, allow_nil?: false\n    get? true # read will only return 1 value, not a list\n    filter expr(id == ^arg(:id))\n  end\nend\n```\n\nThese are the two blocks every `Ash.Resource` will have. `attributes` is the data and `actions` are the operations and workflows it supports.\n\nPost has the UUID primary key that is auto-generated and included. Post also has the created and updated timestamps that are auto-updated. Finally Post includes two standard attributes: title and content. The title attribute has an additional block include to specify it can not be nil. This validation will be performed on all write actions.\n\nDevelopers can write CRUD operations in their sleep. Ash writes them for you. `defaults` is a list of default actions added to the resource. We explicitly have a `create` and `update` action so we can state what attributes we can accpet. In `update` we can change the content of the post, but not the title. We can pass both in `create`. We also include another `read` function called `get`. The default `read` function returns a list of all elements, so we also add an extra `read` action called `get` that returns a Post for the given ID.\n\nWe need to do one more thing before we run our code. Run the following to generate and run migrations. We'll use \"blog_post\" as a name of the migrations but it could be anything.\n```\nmix ash.codegen blog_post\nmix ash.setup\n```\n\n#### Using `Ash.Resource` in code\n\nNow we can run the following in the console:\n```elixir\nalias PetalStackTutorial.Blog.Post\n\n# create post\nnew_post = Post |\u003e Ash.Changeset.for_create(:create, %{title: \"hello world\"}) |\u003e Ash.create!()\n\n# read all posts\nPost |\u003e Ash.Query.for_read(:read) |\u003e Ash.read!()\n\n# get single post by id\nPost |\u003e Ash.Query.for_read(:get, %{id: new_post.id}) |\u003e Ash.read_one!()\n# OR\nPost |\u003e Ash.get!(new_post.id)\n\n# update post\nupdated_post = new_post |\u003e Ash.Changeset.for_update(:update, %{content: \"hello to you too!\"}) |\u003e Ash.update!()\n\n# delete post\nnew_post |\u003e Ash.Changeset.for_destroy(:destroy) |\u003e Ash.destroy!()\n```\n\nHere we can change the model with `Ash.Changeset` and read it with `Ash.Query`. We create a `Ash.Changeset` and `Ash.Query` with the name of the module, the name of the action, and additional options like new fields or filtering criteria. We then pass this to the correct `Ash` function to run the action. All the callbacks are either implented by Ash or derived from the `attributes` and `actions` blocks. All of these actions are reading and writing values to/from the given Data Layer, in this case Postgres.\n\nThis is a lot of code. We can define wrappers with the `code_interface` block in the resource. However for now we'll only define them as part of the domain. Change the Blog doamin to now contain.\n\n```elixir\ndefmodule PetalStackTutorial.Blog do\n  use Ash.Domain\n\n  resources do\n    resource PetalStackTutorial.Blog.Post do\n      define :create_post, action: :create\n      define :list_posts, action: :read\n      define :update_post, action: :update\n      define :destroy_post, action: :destroy\n      define :get_post, action: :get, args: [:id]\n    end\n  end\nend\n```\n\nThe line `define :create_post, action: :create` defines a function called `create_post` that will run the `create` action for the `Post` resource. The other defines work similarly.\n\nNow we can use the Blog domain functions to run the actions of Post.\n```elixir\nalias PetalStackTutorial.Blog\n\n# create post\nnew_post = Blog.create_post!(%{title: \"hello world\"})\n\n# read post\nBlog.list_posts!()\n\n# get post by id\nBlog.get_post!(new_post.id)\n\n# update post\nupdated_post = Blog.update_post!(new_post, %{content: \"hello to you too!\"})\n\n# delete post\nBlog.destroy_post!(updated_post)\n```\n\nThe implementation of these actions are only defined once in the `actions` block of the Post resource. This lays the foundation for deriving more functionality from these actions. We could also create other resources and domains.\n\nIn further sections I'll discuss\n- Integrating Ash models with LiveView forms\n- User and Login with [`AshAuthentication`](https://hexdocs.pm/ash_authentication_phoenix/get-started.html)\n- REST API with [`AshJsonApi`](https://ash-hq.org/docs/guides/ash_json_api/latest/tutorials/getting-started-with-ash-json-api)\n- GraphQL API with [`AshGraphql`](https://ash-hq.org/docs/guides/ash_graphql/latest/tutorials/getting-started-with-graphql)\n\n### Section 4: Users with Ash Authentication\n\nI followed [this tutorial](https://hexdocs.pm/ash_authentication_phoenix/get-started.html) for adding users to a Phoenix app with Ash Authentication and Ash Phoenix Authentication. I did not write additional code.\n\n### Section 5: REST API with Ash JSON API\n\nWe can add a REST API for the Blog domain we added earlier.\n\nI followed [this getting started guide](https://ash-hq.org/docs/guides/ash_json_api/latest/tutorials/getting-started-with-ash-json-api) for adding `AshJsonApi`.\n\nAdd the `ash_json_api` dependency:\n```elixir\ndefp deps do\n  [\n    # ...\n    {:ash_json_api, \"~\u003e 1.0\"},\n    {:open_api_spex, \"~\u003e 3.16\"}, # \u003c- Optional, used later\n    # ...\n  ]\nend\n```\n\nAdd to `.formatter.exs`:\n```elixir\n[\n  import_deps: [\n    # ...\n    :ash_json_api\n  ],\n  #...\n]\n```\n\nAdd `AshJsonApi.Domain` extension to the domain\n```elixir\ndefmodule PetalStackTutorial.Blog do\n  use Ash.Domain, extensions: [\n    AshJsonApi.Domain # \u003c-- add this\n  ]\n  # ...\nend\n```\n\nYou can add a `json_api` block in the domain to declare routes, but for now we'll add them to the resource.\n\nMake the following changes to the Post resource, including adding the extension:\n```elixir\ndefmodule PetalStackTutorial.Blog.Post do\n  use Ash.Resource,\n    domain: PetalStackTutorial.Blog,\n    data_layer: AshPostgres.DataLayer,\n    extensions: [\n      AshJsonApi.Resource # \u003c-- add this\n    ]\n\n  attributes do\n    # ...\n    attribute :title, :string do\n      public? true # \u003c-- add this\n      allow_nil? false\n    end\n    # ...\n  end\n\n  json_api do\n    type \"post\"\n\n    routes do\n      base \"/posts\"\n\n      get :read\n      index :read\n      post :create\n      patch :update\n      delete :destroy\n    end\n  end\nend\n```\n\nAnd that's all you need to add to your model.\n1. Add the `AshJsonApi.Resource` extention.\n2. We made the title attribute public. (Note: the content attribute is implicilty private, meaning it will not show in responses)\n3. Add the `json_api` block which declares a base route, and which HTTP verbs trigger which actions.\n\nNow we need to create a router for our API and forward to it from our main router:\n```elixir\ndefmodule PetalStackTutorialWeb.Router do\n  use PetalStackTutorialWeb, :router\n  # ...\n\n  scope \"/api/json\" do\n    pipe_through :api\n\n    forward \"/\", PetalStackTutorialWeb.JsonApiRouter\n  end\n\n  # ...\nend\n\ndefmodule PetalStackTutorialWeb.JsonApiRouter do\n  use AshJsonApi.Router,\n    domains: [PetalStackTutorial.Blog],\n    json_schema: \"/json_schema\",\n    open_api: \"/open_api\"\nend\n```\n\nIf user curl, Postman, or a browswer on  http://localhost:4000/api/json/posts I see a list all posts.\n\nWhen I go to http://localhost:4000/api/json/open_api I see an OpenAPI JSON spec of my API. If `open_api_spex` is installed then you can added the following (recommended to the dev scope) that will render a SwaggerUI to dispay documentation of your API.\n\n```elixir\n# ...\nscope \"/dev\" do\n  pipe_through :browser\n  # ...\n  forward \"/api/swaggerui\",\n    OpenApiSpex.Plug.SwaggerUI,\n    path: \"/api/json/open_api\",\n    title: \"PetalStackTutorialWeb JSON-API - Swagger UI\",\n    default_model_expand_depth: 4\n  # ...\nend\n# ...\n```\n\nAnd now visiting http://localhost:4000/dev/api/swagger shows that SwaggerUI.\n\nTo add new domains to the API, simply add the domain to the `JsonApiRouter` domain list. The behavoir is already implented in the `attributes` and `actions` blocks of the resource. This is where Ash really starts paying dividends.\n\nFor the sake of completeness, next I want to show how to add a GraphQL API.\n\n### Section 6: GraphQL API with Ash GraphQL\n\nWe can add a GraphQL API for the Blog domain.\n\nI followed [this getting started guide](https://ash-hq.org/docs/guides/ash_graphql/latest/tutorials/getting-started-with-graphql) for adding `AshGraphql`.\n\nAdd the `ash_json_api` dependency:\n```elixir\ndefp deps do\n  [\n    # ...\n    {:ash_graphql, \"~\u003e 1.1.1\"},\n    # ...\n  ]\nend\n```\n\nNote: `AshGraphql` is built on top of `Absinthe`, a GraphQL library for Elixir.\n\nAdd to `.formatter.exs`:\n```elixir\n[\n  import_deps: [\n    # ...\n    :ash_graphql\n  ],\n  #...\n]\n```\n\nAdd the `AshGraphql.Domain` extension to the domain. Also, disable authorization for easy prototyping.\n```elixir\ndefmodule PetalStackTutorial.Blog do\n  use Ash.Domain,\n    extensions: [\n      # ...\n      AshGraphql.Domain # \u003c-- add this\n    ]\n  \n  # ...\n  graphql do\n    authorize? false\n  end\nend\n```\n\nAdd the `AshGraphql.Domain` extension to the resource. Then specify what queries or mutations trigger which actions.\n```elixir\ndefmodule PetalStackTutorial.Blog.Post do\n  use Ash.Resource,\n    domain: PetalStackTutorial.Blog,\n    data_layer: AshPostgres.DataLayer,\n    extensions: [\n      AshJsonApi.Resource,\n      AshGraphql.Resource\n    ]\n  \n  graphql do\n    type :post\n\n    queries do\n      get :get_post, :read\n      list :list_posts, :read\n    end\n\n    mutations do\n      create :create_post, :create\n      update :update_post, :update\n      destroy :destroy_post, :destroy\n    end\n  end\nend\n```\n\nCreate a schema. Here you can create top level queries or mutations, but for now we'll leave them blank.\n```elixir\ndefmodule PetalStackTutorialWeb.GraphqlSchema do\n  use Absinthe.Schema\n  use AshGraphql, domains: [PetalStackTutorial.Blog]\n\n  query do\n  end\n\n  mutation do\n  end\nend\n```\n\nAdd 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.\n```elixir\ndefmodule PetalStackTutorialWeb.Router do\n  use PetalStackTutorialWeb, :router\n  # ...\n  pipeline :graphql do\n    plug AshGraphql.Plug\n  end\n  # ...\n  scope \"/gql\" do\n    pipe_through :graphql\n\n    forward \"/playground\",\n            Absinthe.Plug.GraphiQL,\n            schema: PetalStackTutorialWeb.GraphqlSchema,\n            interface: :playground\n\n    forward \"/\", Absinthe.Plug, schema: PetalStackTutorialWeb.GraphqlSchema\n  end\nend\n```\n\nNow 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.\n\nI can make the following query:\n```graphql\nquery {\n  getPost(id: \"7e9cc199-7c5f-4196-bb7c-d28550d6b7c2\") {\n    id\n  }\n}\n```\n\n### Section 7: TBD\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftyalt1%2Fpetal_stack_tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftyalt1%2Fpetal_stack_tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftyalt1%2Fpetal_stack_tutorial/lists"}