coverghoul
JSON API with Ash but no DB storage needed
Hey Ash Community,
I found ash and the json api and immediately thought of robust documenting APIs like fastapi in python, or spring in java. I’ve been playing with it, and for resources I’m backing into postgres this makes total sense/works. I’m trying to sent up a “calculation only” api (a bunch of recursive math calls), that I don’t need to store into the DB, this is what I came up with:
The domain –
defmodule App.Math do
@moduledoc """
This establishes our ash domain so that we track what is in scope
"""
use Ash.Domain, extensions: [AshJsonApi.Domain]
resources do
resource App.Math.Answer
end
json_api do
routes do
# in the domain `base_route` acts like a scope
base_route "/math/fibonacci", App.Math.Answer do
post :calculate_fibonacci
end
end
end
end
The resource –
defmodule App.Math.Answer do
@moduledoc """
This will showcase an API endpoint that does not store into the database,
but instead does a calculation and returns to the user this value
"""
use Ash.Resource,
domain: App.Math,
extensions: [AshJsonApi.Resource]
attributes do
attribute :problem, :string
attribute :answer, :integer
end
resource do
require_primary_key? false
end
defp calc_fibonacci(0) do
[0 | 0]
end
defp calc_fibonacci(1) do
[1 | 0]
end
defp calc_fibonacci(depth) when is_integer(depth) and depth >= 0 do
[head | tail] = calc_fibonacci(depth - 1)
[head + tail | head]
end
def calculate_fibonacci(depth) when is_integer(depth) and depth >= 0 do
hd(calc_fibonacci(depth))
end
json_api do
type "answer"
end
actions do
action :calculate_fibonacci, :struct do
constraints instance_of: __MODULE__
argument :depth, :integer do
constraints [min: 0]
allow_nil? false
end
run fn input, _context ->
{:ok, %{problem: "fibonacci", answer: calculate_fibonacci(input.arguments.depth)}}
end
end
end
end
When I post to this with -
{
"data": {
"attributes": {
"depth": 3
}
}
}
I get -
[error] ** (ArgumentError) Could not determine a resource from the provided input: %{answer: 2, problem: "fibonacci"}
I could be approaching this the wrong way (and would welcome that feedback/a steer in the right direction), but was hopeful to have something that takes advantage of the swagger/redoc generation ash-json has. Thanks for reading this.
Paul
Marked As Solved
zachdaniel
Ah, right. So in this case since you’re not using something that has a primary key (i.e non-storage backed as you say), your best bet is not to use routes like post and get, as those are meant to following specific patterns described here https://jsonapi.org
Spec compliant JSON:API requires ids. However, we provide a facility for doing generic routes that don’t have to strictly follow those formats, which looks like this:
defmodule Bar do
use Ash.Resource, domain: nil, extensions: AshJsonApi.Resource
json_api do
type "bar"
routes do
post :say_hello
end
end
resource do
require_primary_key?(false)
end
attributes do
attribute(:message, :string, allow_nil?: false)
end
actions do
action :say_hello, :struct do
constraints(instance_of: __MODULE__)
argument(:name, :string, allow_nil?: false)
run(fn input, _ ->
{:ok, "Hello, #{input.arguments.name}!"}
end)
end
end
end
What you can additionally do to simplify is something like this:
defmodule MathAnswer do
destruct [:question, :answer]
use Ash.Type.NewType, subtype_of: :struct,
constraints: [
instance_of: __MODULE__,
fields: [
question: [type: :string, allow_nil?: false],
answer: [type: :integer, allow_nil?: false]
]
]
end
Then you can simplify the resource to require no state/attributes.
defmodule App.Math.Answer do
@moduledoc """
This will showcase an API endpoint that does not store into the database,
but instead does a calculation and returns to the user this value
"""
use Ash.Resource,
domain: App.Math,
extensions: [AshJsonApi.Resource]
actions do
action :calculate_fibonacci, MathAnswerdo
argument :depth, :integer do
constraints [min: 0]
allow_nil? false
end
run fn input, _context ->
{:ok, %MathAnswer{problem: "fibonacci", answer: calculate_fibonacci(input.arguments.depth)}}
end
end
end
defp calc_fibonacci(0) do
[0 | 0]
end
defp calc_fibonacci(1) do
[1 | 0]
end
defp calc_fibonacci(depth) when is_integer(depth) and depth >= 0 do
[head | tail] = calc_fibonacci(depth - 1)
[head + tail | head]
end
def calculate_fibonacci(depth) when is_integer(depth) and depth >= 0 do
hd(calc_fibonacci(depth))
end
end
And you can then use route/3 to define the route:
route :post, "/", :calculate_fibonacci
Also Liked
coverghoul
Thanks for the reply, due to my new born haven’t had the time to try implementing. Hopefully he settles down tonight and I’ll be able to let you know how it goes. Thanks you again for all the help.
Last Post!
Bumbus
Popular in Questions
Other popular topics
Latest Phoenix Threads
Latest Ash Threads
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #hex
- #security









