Implementing optional path prefixes with Phoenix.Router

The Phoenix project under discussion currently has the following routing configuration:

defmodule ProjectWeb.Router do
  use ProjectWeb, :router

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

  scope "/api", ProjectWeb.Api do
    pipe_through :api

    scope "/v1", V1 do
      get "/info", System.InfoController, :show
    end
  end
end

This setup effectively manages GET requests at /api/v1/info.

My current task is to introduce an optional path prefix, /tenant/:tenant_id. If this is included, the value should be added to Plug.Conn with the tenant_id key. And then, a GET request at /tenant/ABC/api/v1/info should be treated identically to a GET request at /api/v1/info.

Given the number of routes in this project, creating duplicates for each route - with and without the optional prefix - is far from ideal.

My current thinking is that the Plug.Router.forward/2 macro could be useful here, but I’m unclear about its proper usage within a Phoenix project. Can anyone share insights or examples of how this could be implemented?

1 Like

Here’s the current solution I’ve come up with. I’ve introduced a new Plug, ProjectWeb.TenantExtractPlug :

defmodule ProjectWeb.TenantExtractPlug do
  def init(args), do: args

  def call(%Plug.Conn{path_info: ["tenant", tenant_id | path_info]} = conn, _) do
    %{conn | path_info: path_info}
    |> Plug.Conn.assign(:tenant_id, tenant_id)
  end

  def call(conn, _), do: conn
end

This Plug is then invoked before the actual router in ProjectWeb.Endpoint:

defmodule ProjectWeb.Endpoint do
  #...
  plug ProjectWeb.TenantExtractPlug
  plug ProjectWeb.Router
end

If there are any alternative methods to achieve this functionality without the need for creating a custom Plug, I would greatly appreciate your insights and suggestions.