OmarGoubail

OmarGoubail

Hello, I am working on an app and I want to separate admin and user routes to different subdomains, I found an article how-to-serve-multiple-domains-in-a-single-phoenix-app It got me part of the way there, but for some reason I can’t get it working the same way for example in the article:

scope "/", MyAppWeb, host: "music." do
  live "/", MusicLive
end

# partial host match - match subdomain `video.`, i.e., matches `video.myapp.com`
scope "/", MyAppWeb, host: "video." do
  live "/", VideoLive
end

# partial host match - match subdomain `admin.`, i.e., matches `admin.myapp.com`
scope "/", MyAppWeb, host: "admin." do
  live "/", AdminLive, :home
  live "/settings", AdminSettingsLive # admin.myapp.com/settings
end

They used “/” in different subdomains, however when I tried to do the same thing, I get a compiler warning this clause cannot match because a previous clause at line 30 always matchesElixir.

  # Public routes (no authentication required)
  scope "/", AvocatoxWeb do
    pipe_through :browser

    # Add other public routes here
    live "/", Home
  end

  # Admin routes (authentication required)
  scope "/", AvocatoxWeb, host: "admin." do
    pipe_through [:browser, :admin]

    live "/", Admin.Home
  end

It works when I go to http://localhost:4000 and http://admin.localhost:4000, but I get the same home function which is the first one.

I mean instead of this markup in the admin.


 def render(assigns) do
    ~H"""
    <div>
      <h1>Admin Home</h1>
    </div>
    """
  end

I get this

 def render(assigns) do
    ~H"""
    <div>
      <h1>Home</h1>
    </div>
    """
  end

I am new to elixir and phoenix and I am enjoying them a lot! Hopefully this is just a gap in my understanding. Thanks for your help in Advance.

Showing Posts 1 to 10

dpreston

dpreston

Try moving your default scope (without a :host) to the end of your list of scopes. Or, at least, after any scopes that have matching paths.

OmarGoubail

OmarGoubail OP

It worked! But I am not sure I understand why.

arcyfelix

arcyfelix

Generally speaking, you don’t want to rely only on the path, because then a user can type admin and be an admin, but I assume you know that (just checking).
I think it would be the easiest to write a Plug and pipe through it.

BartOtten

BartOtten

Because the route configuration you see is being rewritten to functions, subject to pattern matching. When no ‘:host’ is given, the argument will be a wildcard match.

Example

get(_host, /), do: non-admin
get(“admin.”, /), do: admin

Now when you visit “admin.domain.com/“ it will match the first function head. That is not what you want.

Changing the order makes the wildcard match come last, which is what you want.

OmarGoubail

OmarGoubail OP

I am not sure I understand completely what you mean, but I did write an ensure_admin plug, which doesn’t allow normal users to log in only admins that have an account with the role admin.

  pipeline :admin do
    plug :require_authenticated_user
    plug AvocatoxWeb.Plugs.EnsureAdmin
  end

I am handling different cases as I go but I think this is secure, no?

arcyfelix

arcyfelix

You are on the right track (atleast in my head :sweat_smile:).
Here is something that might explain it better than me:
https://thoughtbot.com/blog/redirecting-routes-in-a-phoenix-application-using-plugs

kip

kip

ex_cldr Core Team

It worked! But I am not sure I understand why.

@OmarGoubail, Maybe this will help clarify a little bit:

  1. No matter what DSL you use (like the Plug DSL), eventually everything compiles down to functions.
  2. In Elixir (and all BEAM languages), a function is differentiated by both its name and its arity (arity being how many parameters it takes). You see this commonly written as foo/1 or foo/3 where /1 and /3 is the arity meaning a function with one param, and a function with three params. In Elixir these are different functions.
  3. Pattern matching means that you can write multiple function bodies that have the same name and the same arity. They are then differentiated by pattern matching. This is how the scope macro resolves to a set of functions with the same name and arity - but different pattern matches.

If that clear so far (and by all means comment if it is not clear) then we can now pay our attention to the runtime environment. Lets say we have the following (and this is very unlikely to be what scope actually compiles to, but ultimately it does compile to something like this):

# Called for any request since there is no pattern matching
def plug(MyPlug, conn, scheme, host, path, query) do
  ...
end

# Called if the host is "admin."
def plug(MyPlug, conn, scheme, "admin.", path, query) do
  ...
end

How does the runtime know which of these function bodies to invoke? It checks each function in turn - in lexical order as written in your code.

In our example if will first check def plug(:my_plug, conn, scheme, host, path, query). Since the only pattern match here is on the plug name, it will match. And this is the function body that will run.

This is basically the source of the this clause cannot match error. The first function body matches everything so it will always be invoked.

By moving the “default” function body to the end of the list of function bodies, it will only be invoked if all other function bodies don’t match. Using the example above, we can see that the plug(:my_plug, conn, scheme, "admin.", path, query) will match if the host is admin. And therefore that function body will be invoked, not the default one.

OmarGoubail

OmarGoubail OP

Thank you so much! That helped a lot.

OmarGoubail

OmarGoubail OP

Thank you for the resource! Plugs are kind of confusing to me, I understand the basics but am having trouble grasping it fully. But they seem to simplify a lot of work and open a lot of possibilities.

muelthe

muelthe

Whilst not directly related to your original question, and assuming I’ve understood what you’re aiming to do, I would recommend taking a look at the following with regards to Phoenix and in particular LiveView as far as authentication and authorization checks: Security considerations — Phoenix LiveView v1.2.5

— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
Alvinkariuki
How Can I Optimise Compile Time Dependencies I have been building an elixir application for about 2 years now. Many modules and files ha...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews