Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Phoenix : Having a JSON and HTML representation of the same resource

I'm starting to use Phoenix outside of tutorials for a small use case. So far great, but I'm a bit taken aback by the following.

I have a resource "recording" that I want to access at /api/recordings in JSON and at /recording with a template and the result being HTML.

Ideally the Ecto representation would be unique, and even part of the controller would also be shared?

Right now I either have to have 2 resources recordingAPI and recordingHTML, or 2 controllers and 1 resources.

Any exemple out there? I keep finding one or the other, but not something with the :api pipe and the :browser pipe used for the same resource.

Thanks

like image 697
Antoine Claval Avatar asked Oct 16 '25 11:10

Antoine Claval


1 Answers

You can utilize phoenix accepts plug and a combination of two different views/layouts

# router.ex
defmodule TestExWeb.Router do
  use TestExWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", TestExWeb do
    pipe_through :browser

    get "/recording", PageController, :index
  end

  scope "/api", TestExWeb do
    pipe_through :api

    get "/recording", PageController, :index
  end
end

As you can see, :browser uses :accepts ["html"] while :api uses :accepts ["json"]. You can find this in the private struct of your conn and use it in the controller like this:

defmodule TestExWeb.PageController do
  use TestExWeb, :controller

  def index(%{private: %{phoenix_format: format}} = conn, _params) do
    data = "Hello World"

    render(conn, "index.#{format}", data: data)
  end
end

Now, you just need to tell phoenix how to render your json, html is already taken care of by page.html.eex in layouts, so add the following to your page_view.ex

defmodule TestExWeb.PageView do
  use TestExWeb, :view

  def render("index.json", %{data: data}) do
    %{
      data: data
    }
  end
end

Two drawbacks with this solution:

  • You need this format snippet in every controller (Maybe you can circumvent this with a plug after you "sent" the response)
  • You're using an internal variable of phoenix
like image 131
Jonas Dellinger Avatar answered Oct 19 '25 11:10

Jonas Dellinger