Link helper when working with multiple routers

I’ve been working on splitting my routes up into multiple routers like this:

defmodule MainRouter do
  ...
  
  forward "/api", ApiRouter
end

defmodule ApiRouter do
  ...
  
  scope "/", Api, as: :api do
    scope "/v1", V1, as: :v1 do
      resources "/users", UserController
    end
  end
end

Everything is pretty much working here except for my link helpers. To generate a link for the ApiRouter I can do ApiRouter.api_v1_user_path(conn, index), which will generate a path to /v1/users. The problem is it’s missing the /api portion of the path. I expected it to generate /api/v1/users, but I get why it wouldn’t. I assume the /api portion is being gobbled up by the initial Router and only the remainder of the path is being forwarded to the ApiRouter.

Is there a built in way to get my helpers to output the leading /api or do I need to just roll my own helper method to do this?

1 Like

I’ve been curious in this too, I’ve just been hardcoding the leading paths so far because forward hardcodes the :as option as nil. >.>

I noticed that as well. That’s pretty much what I’ve been doing and was hoping I was just missing something

1 Like

Passing Phoenix.Endpoint's config a url option with path set to /api would do what you’re looking for at the whole-application level, but there’s nothing quite as clean for this situation.

You could do something like:

base_url = YourPhoenixApp.Endpoint.url() |> URI.merge(path: "/api")
ApiRouter.api_v1_user_path(base_url, :index)

which works but duplicates the /api part in a different place.

If you have unlimited free time, maybe consider if there’s some way to traverse through the forward route and generate prefixed helpers automatically.

Considering that I have 6 forwards, not seeing how that would help? ^.^;

You can always use a combo with NGINX as a reverse proxy to send to the correct app / router.
Also I think with you can have your routes in order (top Main, API Bellow), so you can correct the API initial to also “/api” and it will work.

Well mine is structured as a singular web application with libraries for ‘modules’ (different sections of the website), among other libraries. Each module just gives out a router to be forwarded to simply. Using nginx to route differently makes no sense as it is all the same server, and the issue is with *_path results not including the forwarded prefix, so nginx would not help with that either. In my case it’s not an /api route but rather a multitude of things that route around.

That’s very similar to my setup as well. I have several scoped routes that I’d like to split up into individual Routers. Some of those are for various APIs, others are stuff like /admin. I have found several references to using match instead of forward.

1 Like

I was able to get the Plug.Router method to work and it seems to be the way to go. I created a new plug:

# Web.Plugs.Router
defmodule Web.Plugs.Router do
  use Plug.Router

  # You must include these two Plugs or you'll receive an error
  # that's a little hard to track down. It's documented, but I
  # missed it initially
  plug :match
  plug :dispatch

  match "/api/*_", to: Web.ApiRouter
  match "/admin/*_", to: Web.AdminRouter
  match _, to: Web.Router
end

I then updated my endpoint to use my new router plug.

# Web.Endpoint
defmodule Web.Endpoint do
  ...

  # Replace your original router with your new plug
  # Web.Router
  plug Web.Plugs.Router
end

Then within each router I added the full path, which allowed me to use router specific helpers without having to do anything extra.

defmodule Web.ApiRouter do
  ...

  scope "/api", Web.Api, as: :api do
    pipe_through [:api]

    scope "/v1", V1, as: :v1 do
      resources "/users", UserController
    end
  end
end

iex> Web.ApiRouter.Helpers.user_path(conn, :index)
"/api/v1/users"

mix phx.routes now includes the full path per Router as I’d originally wanted.

> mix phx.routes Web.ApiRouter
api_v1_user_path  GET   /api/v1/user     Web.Api.V1.UserController :index

This seems to be the best way to handle this as you can then use your routes and helpers as you normally would without having to jump through hoops to ensure the forward path is being prepended.

6 Likes

Hi @sjoconnor I Loved your solution. But I am wondering is there a way to share channel between the apps
I have below structure:

apps
–proxy_app
----endpoint.ex
----router_plug.ex(Web.Plugs.Router)
----user_socket.ex
----channels
------room_channel.ex
–web_app_1
----router.ex
–web_app_2
----router.ex

proxy_app includes web_app_1 and web_app_2 as dependency. Sometimes I might need to broadcast events from web_app_1 and web_app_2. is there a way I can achieve that?

Thanks in advance

Sorry @tanweerdev, sadly I no longer have access to this app and don’t do much Elixir day so I wouldn’t be a good resource. Best of luck!