I’m working on a Plug application and I’m trying to find an easy way to “mount” it into a Phoenix app or another Plug app.
My Plug application is built on
Plug.Router, and responds to some GET requests to render HTML and POST/PATCH/DELETE from the clients. It’s meant to be a web dashboard and control panel for another package I’ve been working on.
I can successfully run it standalone with
Plug.Adapters.Cowboy.http(...), but I would like to make it embeddable into a host application. The use case is that you’d use the main package for its functionality, and the web control panel would be an optional extra that you can serve from the application itself. This practice is common in Ruby on Rails, for example.
An additional requirement is that it should be possible to mount it at an arbitrary path. My app needs to be aware of this path, or namespace, because it needs it to build the relative paths in its HTML pages (e.g.
Now, I’m having some troubles finding a good way to integrate it.
I’ve found this other package that does something similar (I guess that’s a typo and they actually meant “Using with Phoenix”), but the API seems clunky:
# phoenix router pipeline :exq do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :put_secure_browser_headers plug ExqUi.RouterPlug, namespace: "exq" end scope "/exq", ExqUi do pipe_through :exq forward "/", RouterPlug.Router, :index end
To be honest I don’t like the repetition, and I’m wondering if it’s really necessary, but I’ve tried this approach nonetheless.
In that example,
ExqUi.RouterPlug will use the KW option to set that namespace in
conn.assigns, which will then be used by the
Router. Interestingly, the plug will invoke the router even though the
forward will basically do the same thing a couple of lines later. This works because all the routes in that Plug router end with
halt(), which will make the
forward pointless or, rather, it’s there just to make the Phoenix router’s pattern matching work.
I’ve tried to adapt my own Plug router (or use a separate plug) so that I could
plug it directly into the pipeline, but if I don’t declare anything in
scope it will complain because there are no routes matching the request (which makes sense, I guess).
If I add the
forward statement a few lines later in my
scope and add
halt() in all the routes in my plug router, it works. As I said, however, I don’t really like the repetition.
While I would appreciate comments on whether there is a more elegant variation on that aproach, I can think of two other ways to do what I want.
The first one is to configure the path namespace in the Mix config file. It would work, but I’d have to duplicate that info in two places.
The alternative is that I could just
forward to the router, and then extract the first path fragment and set it in
conn.assigns for later use.
Is there a better and established way?