Should API Server only provide information which is required by front-end as a response?

Hello. I have a question about design of phoenix api server.
I am developing a web application, and using phoenix as a backend server for it. I saw some open source repositories as examples, but I still don’t know what is a good way to determine a design. I will take papercups chat api as an example of phoenix server.

Question

I think phoenix consists of controller, view, and schema file, query file. In the below examples, elixir reads information from database with query and schema, pass the information to controller, and shape it as a json in the view. The view returns most of user information except for preloaded data such as settings, profile, then I got 2 questions of it.

  1. Should API Server only provide information which is required by front-end as a response?
    The view contains most of user information as I said. Although I’m not sure how the front-end is, I don’t think the all fields of render("user.json", %{user: user}) are necessary in all cases to handle user information in front-end. Do I not need to prepare a view per a kind of request from front-end?

  2. Should I refrain from frequent use of preload?
    get_user_info/2 returns not only user information but also preloaded data. But the preloaded data are unused in view. Should I prepare another function which does not preload data In such case? Or does it depend on information like table size, count of foreign keys, and so on?

Query

users.ex

@spec get_user_info(binary(), integer()) :: User.t() | nil
def get_user_info(account_id, user_id) do
  User
  |> where(id: ^user_id, account_id: ^account_id)
  |> Repo.one()
  |> Repo.preload([:profile, :settings])
  end

Schema

user.ex

schema "users" do
  field(:email_confirmation_token, :string)
  field(:password_reset_token, :string)
  field(:email_confirmed_at, :utc_datetime)
  field(:disabled_at, :utc_datetime)
  field(:archived_at, :utc_datetime)
  field(:role, :string, default: "user")
  field(:has_valid_email, :boolean)

  has_many(:conversations, Conversation, foreign_key: :assignee_id)
  has_many(:messages, Message, foreign_key: :user_id)
  belongs_to(:account, Account, type: :binary_id)
  has_one(:profile, UserProfile)
  has_one(:settings, UserSettings)

  has_many(:mentions, Mention)
  has_many(:mentioned_conversations, through: [:mentions, :conversation])
  has_many(:mentioned_messages, through: [:mentions, :message])

  pow_user_fields()

  timestamps()
end

Controller

user_controller.ex

@spec show(Plug.Conn.t(), map) :: Plug.Conn.t()
def show(conn, %{"id" => id}) do
  user = ChatApi.Users.get_user_info(conn.assigns.current_user.account_id, id)

  render(conn, "show.json", user: user)
end

View

user_view.ex

def render("show.json", %{user: user}) do
  %{data: render_one(user, UserView, "user.json")}
end

def render("user.json", %{user: user}) do
  case user do
    %{profile: %UserProfile{} = profile} ->
      %{
        id: user.id,
        object: "user",
        email: user.email,
        created_at: user.inserted_at,
        disabled_at: user.disabled_at,
        archived_at: user.archived_at,
        full_name: profile.full_name,
        display_name: profile.display_name,
        profile_photo_url: profile.profile_photo_url,
        role: user.role
      }

    _ ->
      %{
        id: user.id,
        object: "user",
        email: user.email,
        created_at: user.inserted_at,
        disabled_at: user.disabled_at,
        archived_at: user.archived_at,
        role: user.role
      }
  end
end

Thank you.

The best way to think about the design of the app is to think of Phoenix as nothing more than a web layer on top of your application. Phoenix Is Not Your Application is a must-see on the topic. Generally, try to focus on the core behaviour of the application. Phoenix’ Context guide might be a helpful read too.

Sounds like the frontend for the app will be a single-page app written in (some flavour of) JS. If this is the case, such apps keep the state in the browser and they usually fetch the user with the sign-in request. This means that you don’t have to return a rich response with all the preloads for each request that you’re handling.

The API for the frontend app will be quite focused, which is good, as it’s much easier to build an API for a specific need (e.g. an API for a browser/mobile app) than to build something general (e.g. a public API for a product). In my personal project, I’m building an app that renders HTML pages and also has a mobile app counterpart. The backend app exposes a tailored API for the mobile app: it’s faster to build and easier to maintain. I’d suggest starting that way. If you find yourself having to adjust the API because the frontend needs more/different data but you actually don’t need to add any new features to handle those requests, then it’s probably a good idea to consider adding GraphQL support to your API.

2 Likes

Thank you for the reply :smiley:
I will read the page you sent and watch the youtube.