Accept JSON Object in Ash Graphql
Here's how to send json as an input variable to a graphql endpoint in ash_graphql.
Endpoint
Ash is all about the declarations. I hereby declare that we'll write an endpoint that takes a JSON object. For our purpose, json just means an object, or a non-scalar.
defmodule MyEntity do
# ...
actions do
create :take_things do
argument :things {:array, :map}
manual TakeThings
end
end
graphql do
# ...
mutations do
create :take_things, :take_things
transaction? true
manual TakeThings
end
end
end
defmodule TakeThings do
def create(changeset, _opts, _context) do
things = changeset.arguments.things
things |> Enum.map(fn thing ->
%{
id: thing["id"],
subthings: thing["subthings"] |> Enum.map(fn subthing ->
%{
id: subthing["id"]
}
end)
}
end)
# write... not shown
end
end
What are we looking at here? We have an entity that is set up (much not shown, see ash_graphql docs for more) to be exposed in a graphql endpoint. We've added a new mutation, takeThings
. It takes an argument as input, things
. This is typed as an :array
of :map
, or an array of objects.
I wasn't able to get the maps with graphql option working. But it would have been nicer to have a struct there, as a more specific type, compared to a map. Seems like it should be possible. Let me know if you figure it out.
My use case (and a likely one for a blob of schtuff), is that you'll want some sort of manual handling of the data. It's going to be something more complicated that the usual single entity CRUD. That's what's shown here. In the TakeThings
module, we've implemented a create
function to match the action type. The things
argument is an array of maps when access here. The keys are strings and are thus accessed that way.
Client
That should about do it for the server. What about the client? To make a request to such an endpoint, let's set up the input and fetch:
async function saveThings(theThings) {
return fetch(MY_SERVER_HOST + '/graphql', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
mutation takeThings($input: TakeThingsInput!) {
takeThings(input: $input) {
result {
id
subthings {
id
}
}
errors {
code
fields
message
}
}
}
`,
variables: {
input: {
things: JSON.stringify(theThings)
}
}
})
})
}
What's interesting here? We are set up to call the mutation in our query string. The $input
variable is of a type that's generated by Ash, TakeThingsInput
. When we set the input
, it has a key of things
inside of it. That matches the things
argument in our action. The value of things
is stringified. This happens before and in addition to the stringification of the entire post request body.
Those are the essential pieces needed to set up Ash Graphql to take a JSON object in a POST request.