Umbrella App routing with Plug.Router app & Phoenix app

I’ve set up my umbrella app into three apps - simple Plug app, Phoenix app (called Chemistry), and database.

The first app simply receives a request, queries the database for the appropriate record, and redirects to an external url on that record. The second phase of the project was making this app into part of an umbrella app structure and separating the database into its own app. Additionally, I created a Phoenix app to handle uploading images.

Goal: do not load the entire Phoenix app if it’s not necessary. My idea was to set up routing so that the request is forwarded from the Plug app to the Phoenix/Chemistry app.

I’ve not been successful in implementing it this way - only in the opposite direction. When I forward from the Plug app - forward '/chemistry', to: Chemistry.Router - it returns the following compilation error:

(UndefinedFunctionError) function Chemistry.Router.init/1 is undefined (module Chemistry.Router is not available)

Am I not correctly importing the module? Any tips on the best way to handle routes for two applications?

Thanks!

1 Like

Your phoenix app is working if you visit one of its routes bypassing the plug app?

1 Like

Correct. The apps are on different ports (4000 & 4004) so I can access the routes by their corresponding port, but I’d like to go to one port (for the plug app) and be able to access the routes in the Phoenix app.

1 Like

I think the problem is that the phoenix router is not a plug, whereas forward is expecting a plug, I think you need to give it the endpoint and not the router?

3 Likes

Ah, thanks @amnu3387! As you suggested, forward "/chemistry", to: ChemistryWeb.Endpoint works. However, it doesn’t load the assets, and submitting forms fails because they’re missing the chemistry/ prefix. Might have to figure out a different routing solution.

Thanks for your help!

Forward will eat the prefix, I think you want something more in line with what is being done here in this sample app

So one plug above where you pretty much raw match on the path, followed by a call to the appropriate phoenix endpoint or plug (in your case). Not sure this solves the assets, but at least the path will be passed entirely

The phoenix router is most definitely a Plug :slight_smile: You can forward to a router as much as you like so that’s not an issue.

1 Like

Ah - I just skimmed through the Router src and didn’t see the init method?
So what’s missing there? The app being a dependency on the other umbrella app?

We use a very similar setup, here’s how we structured the app:

The plug app defines a router that uses match <path>, to: <plug> to forward requests without altering the path.

  match "/events/*_", to: Events.Router
  match "/exq/*_",    to: Exq.Router
  match "/health",    to: Health.Router
  match "/report/*_", to: Report.Router
  match "/stats/*_",  to: Stats.Router

Then each of the umbrella apps defines a Router module, with Phoenix.Router for the nice url helpers.

We add a TestEndpoint module in each umbrella app under /test/support/test_endpoint.ex and load it conditionally in the Application module based on Mix.env

  def start(_type, _args) do
    children = case Mix.env do
      :test -> [Health.TestEndpoint]
      _ -> []
    end
    Supervisor.start_link(children, strategy: :one_for_one, name: Health.Supervisor)
  end

This allows you to develop each umbrella app in isolation, then stitch them all together with the plug router.

The plug router has to depend on every other app to forward to them:

 defp deps do
    [
      {:events, in_umbrella: true},
      {:health, in_umbrella: true},
      {:stats, in_umbrella: true},
      {:reports, in_umbrella: true},
   ]
  end
17 Likes