How to properly dispay relationships in the response body with AshJsonApi

Hello!

Sorry for being so talkative these days!

I’m trying to send relationships in a AshJsonApi response body, but I probably missed something.

I’ve wrote the library documentation, and this post: How to use AshJSONAPI / OpenUI to fetch key relationships for index (:read action) - #8 by MartinVolerich

Both seems to explain that sending relationships in the API response is as easy as adding an includes field, followed by one or a list of these relations:

# User Resource

  json_api do
    type "user"
    includes :emotions
  end

(my routes are defined at Domain level), as this is the recommended design in the official documentation.

I’m pretty sure my :read action returns relationships, because I tried it within IEX.

# User Resource

  actions do
    […]

    read :read do
      prepare build(load: :emotions)
    end
  end

Then, my full API response looks like that:

{
  "data": [
    {
      "attributes": {
        "name": "Guillaume"
      },
      "id": "e8dd4eef-5890-4a6b-b8aa-385fbfaef146",
      "links": {},
      "meta": {},
      "type": "user",
      "relationships": {
        "emotions": {
          "links": {},
          "meta": {}
        }
      }
    },
    {
      "attributes": {
        "name": "Hervé"
      },
      "id": "fad98c0e-d9bf-4210-9358-4a3d8cd45448",
      "links": {},
      "meta": {},
      "type": "user",
      "relationships": {
        "emotions": {
          "links": {},
          "meta": {}
        }
      }
    }
  ],
  "links": {
    "self": "http://localhost:4000/api/v1/users?includes=emotions"
  },
  "meta": {},
  "jsonapi": {
    "version": "1.0"
  }
}

I can see the field relationships with the emotions relation inside it.
But it is empty (again, I’m sure some relationships are populated for my user “Hervé”).

If I remove the includes :emotions in my json_api definition at the Resource level, I have the same output, with the relationships field, and an empty emotions field inside.

The only way I currently was able to retrieve my User’s :emotions in the AshJsonApi response is by adding the :emotions as a default field:

User Resource

  json_api do
    type "user"

    default_fields [
      :id,
      :name,
      :emotions
    ]
  end

This way, I don’t even have to keep the includes :emotion declaration, and here is the output:

{
  "data": [
    {
      "attributes": {
        "name": "Guillaume",
        "emotions": []
      },
      "id": "e8dd4eef-5890-4a6b-b8aa-385fbfaef146",
      "links": {},
      "meta": {},
      "type": "user",
      "relationships": {
        "emotions": {
          "links": {},
          "meta": {}
        }
      }
    },
    {
      "attributes": {
        "name": "Hervé",
        "emotions": [
          {
            "id": "5361ed70-c94d-412c-912f-2654e9d47556",
            "name": "Fierté",
            "basic_emotion_id": "7399f301-7a58-4175-85ac-ccd40e44af4a"
          },
          {
            "id": "155e8f1a-ef53-4418-8cf7-6c2083e0776c",
            "name": "Contentement",
            "basic_emotion_id": "7399f301-7a58-4175-85ac-ccd40e44af4a"
          },
          {
            "id": "9654784d-5e1b-407d-8c5e-5309ed2381c1",
            "name": "Frustration",
            "basic_emotion_id": "d0952031-fd83-4717-bc8c-5b00f67997ea"
          }
        ]
      },
      "id": "fad98c0e-d9bf-4210-9358-4a3d8cd45448",
      "links": {},
      "meta": {},
      "type": "user",
      "relationships": {
        "emotions": {
          "links": {},
          "meta": {}
        }
      }
    }
  ],
  "links": {
    "self": "http://localhost:4000/api/v1/users"
  },
  "meta": {},
  "jsonapi": {
    "version": "1.0"
  }
}

As you can see, my User “Hervé” has some :emotions :slight_smile:

But I guess this is not the right way to get them in the JsonApi response, and that I should be able to retrieve them in the relationshipsemotions field of the response.

P.S. to be more exhaustive, here is my emotions relationship delacation:

# User Resource

  relationships do
    many_to_many :emotions, Emotion do
      public? true
      through UserEmotion
      source_attribute_on_join_resource :user_id
      destination_attribute_on_join_resource :emotion_id
    end
  end

And here is my /users’s index route declaration:

#Users Domain

      base_route "/users", User do
        index :read
      end

NOTE: I had the same output with a belongs_to relationship before trying this one.

includes defines what includes may be requested. The API follows the jsonapi.org specification. Including related data is done via a query parameter, like so ?include="emotions"

2 Likes

Thank you for your answer!

But I already tried it and I’m getting an Invalid Includes error:

[info] GET /api/v1/users
[debug] Processing with CesizenWeb.AshJsonApiRouter
  Parameters: %{"include" => "\"emotions\""}
  Pipelines: [:api]
[debug] QUERY OK source="tokens" db=0.2ms idle=319.3ms
SELECT TRUE FROM "tokens" AS t0 WHERE (t0."purpose"::text::text = $1::text::text) AND (t0."jti"::text::text = $2::text::text) LIMIT 1 ["revocation", "310jdnmcmb5o5to3ss000981"]
↳ anonymous fn/5 in AshSql.AggregateQuery.add_single_aggs/5, at: lib/aggregate_query.ex:119
[debug] QUERY OK source="tokens" db=0.2ms idle=320.5ms
SELECT t0."updated_at", t0."created_at", t0."expires_at", t0."extra_data", t0."jti", t0."purpose", t0."subject" FROM "tokens" AS t0 WHERE (t0."jti"::text::text = $1::text::text) AND (t0."purpose"::text::text = $2::text::text) AND (t0."expires_at"::timestamp::timestamp > $3::timestamp::timestamp) ["310jdnmcmb5o5to3ss000981", "user", ~U[2025-05-20 17:46:31.012040Z]]
↳ anonymous fn/3 in AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:785
[debug] QUERY OK source="users" db=0.2ms idle=321.2ms
SELECT u0."id", u0."name", u0."role", u0."inserted_at", u0."updated_at", u0."email", u0."confirmed_at", u0."hashed_password" FROM "users" AS u0 WHERE (u0."id"::uuid::uuid = $1::uuid::uuid) ["e8dd4eef-5890-4a6b-b8aa-385fbfaef146"]
↳ anonymous fn/3 in AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:785
[debug] invalid_includes: Invalid Includes | Invalid includes: [["\"emotions\""]]
[info] Sent 400 in 4ms

I must admit that I’m lost.

EDIT: to make it clear, it’s the same output without the quotes around the relation name in the request (it just removes the escaped quotes in the error).

Did you also include emotions in the includes list on the resource? You have to do both.

1 Like

OK I found my issue:

I had to query /api/v1/users?include=emotions

And I had to put :emotions in a list like that:

  json_api do
    type "user"
    includes [:emotions]
  end

It doesn’t work if the relation is not under square brackets like that:

  json_api do
    type "user"
    includes :emotions
  end

Since I had no compilation error, I thought it was OK. Sorry about that, and thank you for you help… :slight_smile:

Not your fault at all, thats definitely a bug. It should either be an error or it should wrap the list. Would you mind opening an issue?

It’s done: `includes/1` invalid argument not raising compilation error · Issue #341 · ash-project/ash_json_api · GitHub

I hope in the future to be able to contribute more!