Complete example of JSON API format for a Phoenix JSON resource?

I’m new to Elixir and Phoenix, but I’m getting things to work after going through a tutorial on Udemy… I’m impressed by the performance and excited about exploring, but there are lots of rough spots in the documentation that make this a slow process.

For the ease of implementation, I would like to format my JSON responses using the JSON API spec (http://jsonapi.org/). It’s particularly useful because each resource can tell the client (e.g. an iOS app) where the related resources are. I haven’t been able to get any of the packages working for transforming my responses to JSON API format, so I’ve decided to just edit the JSON views… low tech. I’m going for something like this:

{
  "meta": {
    "pagination": {
      "total_entries": 169,
      "page_size": 16
    }
  },
  "links": {
    "self": "/api/somethings?page=1",
    "prev": null,
    "next": "/api/somethings?page=2",
    "last": "/api/somethings?page=16",
    "first": "/api/somethings"
  },
  "data": [
    {
      "type": "something",
      "id": 1,
      "attributes": {
        "status": 1,
        "name": "Bla bla",
        "description": "bla bla bla"
      },
      "links": {
        "self": "/api/somethings/1"
      }
    }
  ]
}

However, I’m running into problems with the links on the individual items in the collection. For example, the index.json view should list a paginated collection of resources, and thanks to the Scrivener plugin, I was able to get links in place for the collection so the client will know the links for next, prev, first, last… but how can I generate links for the individual items? The path helpers require the conn variable, e.g.

something_path(conn, :show, something)

I can twiddle with this to thread the conn variable from the controller and through all the madness in the view module, but it feels really hacky. Does anyone have an example of doing this anywhere?

1 Like

As alternative you can pass in MyApp.Endpoint instead of the conn variable, passing in conn however is preferred if available.

https://hexdocs.pm/phoenix/Phoenix.Router.html#module-helpers

It doesn’t specifically show this example but does say conn_or_endpoint. If you’d like to help out, maybe send a PR adding an example to the documentation :slight_smile:

Also, its not uncommon to just pass the conn variable in the view

1 Like

Brilliant! Thank you!

To be clear (since this confused me in updating from Phoenix 1.2 to 1.3), my view needs to reference the WEB version of the module, i.e. MyAppWeb.Endpoint in a manner like so:

  def render("something.json", %{something: something}) do
    %{
      type: "something",
      id: something.something_id,
      attributes: %{
          # ... etc ....
      },
      links: %{
        self: something_path(MyAppWeb.Endpoint, :show, something)
      }
    }
  end

The other gotcha I experienced is because I’m connecting to an existing database which does NOT use a simple “id” key. So the path helper function failed with an error. The solution to that second error was in https://hexdocs.pm/phoenix/Phoenix.Param.html – I had to add this to my model:

@derive {Phoenix.Param, key: :something_id}
1 Like

It’s not obvious from a generated view module, but the conn is automatically merged into the view assigns, so you can always access it from a render function in your view:

  def render("index.json", %{users: users, conn: conn}) do
    %{data: render_many(users, UserView, "user.json"), links: %{self: user_url(conn, :index)}}
  end
1 Like

I thought that was the case just couldn’t find the documentation to support it, does that also apply to the render one and many versions?

1 Like

There’s a bit more complexity here when it comes down to creating links to RELATED resources. E.g. if you’re viewing a post, the response should include a list of relationships that include links to the post’s comments or author. However… how can this be done when the corresponding “comments” or “author” objects have not been fetched? Well… I figured out a way to do it given that for a given object, I have the foreign key to the related object.

It turns out you can supply a struct for the related resource and populate it with the foreign key – and that’s all that the route helper needs as the 3rd argument to generate the link:

relatedresource_path(MyAppWeb.Endpoint, :show, %MyApp.SomeContext.RelatedResource{id: thisresource.foreign_key_id})

Or you can pass the ID straight. If a struct is passed in it just takes it’s id field and uses that. :slight_smile:

1 Like