Has anybody tried to use Ash + Inertia.js together?

I want to use Ash as the backend and Inertia.js as web ui. has anyone done this before? Any suggestions are welcome.

Cheers,
Zhen Zhang

Looking through the Phoenix docs it seems like what all you’d do differently is call into Ash instead of Ecto, and would need an adapter for representing errors. Not sure if they have a behavior or protocol you can implement, but it would be pretty easy to write something if they do.

inertia-phoenix doesn’t have such a behavior or protocol. assign_errors() calls compile_errors!() which converts the changeset errors into a shape inertia recognizes.

BTW, they require that schemas be serialized to json via @derive Jason.Encoder. what does Ash do for this?

:cry: Ash does not define a JSON serializer like that automatically, primarily because it doesn’t really make a lot of sense to do given that different things can be loaded in different cases etc. However, if you must go that approach, you can derive a serializer if you like or use a tool like ash_json to derive one for you.

It is problematic that Inertia-phoenix integrates directly with Ecto in my opinion instead of providing behaviors that would let it work with anything. Likely integrating Ash with intertia-phoenix would necessitate making improvements to inertia-phoenix.

Wish I had a better answer for you :cry: Ultimately, this is the fundamental flaw that arises when libraries use Ecto.Schema as the integration point, instead of defining a behavior or protocol and making an Ecto.Schema implementation for it.

3 Likes

Hey Zach, i’m reviving this thread cause i got curious with something, inertia assign_errors also accepts an elixir map, i took this map example from their docs

%{
  "name" => "Name is required",
  "password" => "Password must be at least 5 characters",
  "team.name" => "Team name is required",
}

Would be possible to turn Ash.Changeset into a map with these structure ? If so, we could adapt ash with inertia

1 Like

You can grab the errors, use Ash.Error.to_error_class to aggregate them, get class.errors which is a list group them by their field and path keys as a string, yes :slight_smile:

1 Like

Is there a way to retrieve validation error messages, such as those generated by allow_nil? false and Ash.create!, and incorporate them into a map? I’m looking for functionality similar to what happens when you submit an AshPhoenix.Form and it’s validated, resulting in a set of error messages. How can I achieve something like this? There’s a try/rescue example in the docs but i was looking forward to something more elegant than that.

That’s what I was describing above. You can call Ash.Error.to_error_class on the errors in a changeset or on a raised error and group up the errors that are in the errors key.

My suggestion would be to actually leverage AshPhoenix.FormData.ToError to extract errors that have known-to-be-safe messages.

error_or_errors
|> Ash.Error.to_error_class()
|> Map.get(:errors)
|> Enum.flat_map(fn error -> 
  if AshPhoenix.FormData.ToError.impl_for(error) do
    error
    |> AshPhoenix.FormData.ToError()
    |> List.wrap()
    |> Enum.map(fn {field, message, vars} -> 
      {Enum.join(error.path ++ [field], "."), replace_vars(message, vars)}
    end)
  else
    []
  end
end)

defp replace_vars(message, vars) do
  Enum.reduce(vars || [], message, fn {key, value}, acc ->
    String.replace(acc, "%{#{key}}", to_string(value))
  end)
end
1 Like

This module really exists?

Sorry it’s just .Error not .ToError AshPhoenix.FormData.Error — ash_phoenix v2.1.18

1 Like

I too am interested in using ash with react via Inertia. I opened a discussion regarding this on Gh to learn more about it. Got a great response but also realized my current Ash knowledge is not a a level where I could meaningfully contribute to it.
I’ll just link it here and hope to follow the progress of it, hope it be positive for both Ash and Inertia phoenix.

Just wanted to chime in to point out that inertia-phoenix has been updated to provide a protocol for dealing with errors: Inertia.Errors — Inertia v2.3.0

Should allow for translating back and forth between Ash and Inertia. I haven’t played with it yet so I can’t give an example but wanted to make sure people knew it was available.

1 Like

That’s great! I think we can make inertia an optional dependency of ash_phoenix in that case and implement the protocol there. Users should then be able to just drop an Ash.Changeset, Ash.Query, or Ash.ActionInput in. I don’t have time to PR it but it would be a welcome addition!

@mbuhot is giving a talk about Inertia.js at Elixir Sidney this week. It’s online, so if the timezone isn’t a problem, you should check it out. Elixir Sydney - March 2025 edition · Zoom · Luma

3 Likes

@mbuhot is giving a talk about Inertia.js at Elixir Sidney this week.

Youtube link for the meetup talk: https://www.youtube.com/watch?v=PEtGNztJaHg

I’ve been experimenting with InertiaJS and Phoenix for an implementation of the RealWorld Demo app.
GitHub - team-alembic/realworld-phoenix-inertia-react (sorry for lack of docs!)

Overall it feels very simple, like good old MVC, but with React as your UI layer.

Having the page props immediately available so you never have loading states or error states from API requests is so nice.

The useForm hook is excellent, you submit your form data and let the server either respond with errors or redirect to a new page showing the result.

The backend is using Ash, so I followed @zachdaniel’s suggestion to implement the Inertia.Errors protocol which handles mapping errors from the domain layer onto form field errors.

The only issue I’ve run into was due to the way Inertia handles history navigation. By default it re-renders the page using the props originally provided when page was first loaded. In my case it caused inconsistency because parts of the page that would update in realtime by listening on a Phoenix Channel, but Inertia was unaware of those changes. I solved the problem by sending fresh state to the client after the Phoenix Channel joins.

To make it really nice I’d like to generate TypeScript types (maybe Zod schemas?) for any complex data types being rendered as props or sent over Phoenix Channels.

9 Likes

@mbuhot Thanks for the excellent talk! The repo you shared also helps answer a bunch of questions I had regarding simple things like flash messages and form handling. It’s going to save me a bunch of time trying to figure that stuff out on my own.

4 Likes