Adding a match_path function like in Plug



There is currently no easy way in Phoenix to retrieve the path of the route against which a request was matched. For example, if you define a get "/users/:id" route and you receive a GET /users/1 request, you have no easy solution to retrieve that the route was /users/:id. You’d only have access to conn.request_path which would be something like /users/1 in such case.

Accessing the route can be really useful in the following cases:

  • logging - logging your requests by adding a route metadata in order to filter quite easily by route.
  • metrics - tagging your request metrics with the route so that you could for example check the performance of your app per route. At my company, we are currently tagging our metrics by the request_path but it means that each id will create a different “metric/graph”. Given that our metrics aggregator pricing increases with the cardinality of the metrics, you can easily see how that could create some issues.
  • rate_limiting - creating a route-based rate limiting.

This issue has been brought up a few time on the forum (Get match path in Phoenix, Access matched Route from phoenix Conn) and since a similar functionality has been merged into Plug (, I think it would be great to have such a feature in Phoenix as well. Please let me know if I’m mistaken here.

I quickly experimented locally and I think the following change in the Router would be enough to solve this:

  defp build_match({route, exprs}, known_pipelines) do
-   %{pipe_through: pipe_through} = route
+   %{pipe_through: pipe_through, path: route_path} = route

      prepare: prepare,
      dispatch: dispatch,
      verb_match: verb_match,
      path: path,
      host: host
    } = exprs

    {pipe_name, pipe_definition, known_pipelines} =
      case known_pipelines do
        %{^pipe_through => name} ->
          {name, :ok, known_pipelines}

        %{} ->
          name = :"__pipe_through#{map_size(known_pipelines)}__"
          {name, build_pipes(name, pipe_through), Map.put(known_pipelines, pipe_through, name)}

    quoted =
      quote line: route.line do

        @doc false
        def __match_route__(var!(conn), unquote(verb_match), unquote(path), unquote(host)) do
+         var!(conn) = update_in var!(conn).private,
+           &Map.put(&1, :phoenix_route, unquote(route_path))

          {unquote(prepare), &unquote(Macro.var(pipe_name, __MODULE__))/1, unquote(dispatch)}

    {quoted, known_pipelines}

and then add a function to the Router:

defmodule Phoenix.Router do
  @doc """
  Returns the path of the route that the request was matched to.
  @spec match_path(Plug.Conn.t) :: String.t | nil
  def match_path(%Plug.Conn{} = conn) do
    Map.fetch(conn.private, :phoenix_route)

path = Phoenix.Router.match_path(conn)

I’d be willing to open a PR for that but since I’m not too familiar with Phoenix source code, I’d love to get feedback on whether you feel that change should go into Phoenix and whether the change I suggested above makes sense or not.




Did you ever create a PR? I think this would be a great addition.

Side note: Will this work when forwarding to other routers as well?

1 Like

Hey @fredr!

Yes I did create one a few days ago

AFAIK I did not actually test that. Though I guess you could checkout the PR and give it a try :wink:

1 Like