How to match uppercase and lowercase route name in Phoenix

I’ve a phoenix route in my router.ex file, say I’ve get "/users", UserController, :all_users I would like to send "/users" even when user types the url in uppercase or camel case, for example "/UsErS". How can we do this?

1 Like

Use a catch all route, normalize path info and redirect.

1 Like

Agreed, although @HappyBee you’ll want to add probably a URL parameter or something to indicate that the normalization process has been attempted, so that if they pass in an entirely invalid route it won’t just redirect loop indefinitely.

1 Like

Though I have to admit, during diner I had the idea to normalize in a plug even before the router is hit.

Not sure if that would work though, not doing enough of phoenix to know that, also in general I’m not a friend of normalisation…

Yeah, that’s an interesting point. @HappyBee could add a plug to the endpoint upstream from the router that downcased everything. Only snag would be any situation where there was some sort of user provided value in the URL path that was case sensitive. That isn’t a good idea generally though, so it should be easy to avoid.

Thanks @benwilson512 @NobbZ appreciate your help.

@chrismccord what would be a better solution?

I case across this solution for case sensitive routing in Node.js. I wonder whether there is a similar solution/plug in Phoenix

I’m not sure if dealing with case-ness is the job of the application server since lowercase and uppercase are really distinct characters in URL but I agree that this is not the case for humans…

If you have a front faced web server (or a reverse proxy), I think that it’s more a job to be done there with rewrite rules for example.

In this example it’s done by express in the application server side indeed…
I don’t think there is a default option to ignore case in Phoenix. (Could be a proposal though)

But I’m sure that thanks to the Plug architecture, it could be done easily with plugs…
Try to play around a will come back if I got some results…

1 Like

I’ve created a plug and inserted it in the browser’s pipeline

defmodule MyApp.Plugs.CaseInsensitiveRoutes do
    import Plug.Conn

    def init(opts) do
        opts
    end

    def call(conn, _params) do
        conn = conn
        |> Map.put(:path_info, Enum.map(conn.path_info, fn key -> String.downcase(key) end))
        |> Map.put(:request_path, String.downcase conn.request_path)

        IO.inspect conn
    end
end

But it’s not doing what I intended to do. Any advice would be greatly appreciated.

Hi @HappyBee,

I just tried with a bare phx.new app…
Well, it works…
Here is an example Module Plug (put in a plug folder for example):
(It’s mostly the same as your example.)

# ci_routes/lib/ci_routes_web/plug/ci_routes_plug.ex
defmodule CiRoutesWeb.CIRoutesPlug do
  def init(opts), do: opts

  def call(conn, _params) do
    conn
    |> Map.put(:path_info, Enum.map(conn.path_info, &String.downcase(&1)))
    |> Map.put(:request_path, String.downcase(conn.request_path))
    |> IO.inspect() #todo: remove for production
  end
end

Just be sure to call it before the Router in your endpoint.ex file:

defmodule CiRoutesWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :ci_routes

  # truncated

  plug Plug.Session, @session_options
  plug CiRoutesWeb.CIRoutesPlug #To add here
  plug CiRoutesWeb.Router
end

However, in the browser URL input box (at the top), the path will be still in the orginal uppercase/spongebob case since the URL it’s not rewritten in the web server level…

It might be interesting to discuss with @chrismccord for a default implementation of case insensitive-ness either in the plug level or even in the Router file like so:

#router.ex
scope "/blog", CiRoutesWeb do
    pipe_through [:browser, :ci_routes] #or any other relevant name like :case_insensitive_urls

    ressources "/", BlogController #might be useful when using slugs
  end

@Sanjibukai0 You’re freaking genius, that’s the way to do it, we’ve to include the plug before hitting the Router plug… I had the same thought, but I’ve no idea how to implement it.

Thanks a lot for the solution, I hope there would be an easy/simple solution rather than including the plug in endpoint.ex. Yeah, I understand not everyone have same requirements as I do, but it’s good to have default options :slight_smile:

1 Like

I mean you literally already came up with the exact same solution :sweat_smile: in the meantime…

I don’t know if it can be done easily in the router level without leveraging globs (catchall, wildcard etc…)

Yeah, you’re right… we’ve to work at global level(also restrict this action on certain paths using wildcards, well I don’t know what else say)

Note that if you’re using something like phx.gen.auth that has confirmation token routes like

get "/users/confirm/:token", UserConfirmationController, :confirm

Then that plug will break the token (because the token is case-sensitive), I’m not sure about a clean way to exclude routes that are similar.

Indeed…
But I guess that for registration related routes one can simply remove the plug within the pipe_through directive…