Help defining URL Rest structure for nested element

Hello everyone I’m creating an example phoenix app just for learning. My example app is a real estate web portal, so I’m gonna publish places (houses, apartments, etc). I have defined the following entities
Place (house, apartment) 1 → n rooms

So there is a 1 to n relationship between house and rooms and is a composition (so the rooms doesn´t exist if the house doesn’t exist).

My question is, Should be the url structure like following

/houses/house_id/rooms/room_id

If so assuming I have created both entities and liveviews

mix phx.gen.live Places House houses address:string rooms:integer image_path:string
mix phx.gen.live Places Room rooms name:string image:string

and for the last one I created a migration to improve the relationship between houses and rooms

...
    create table(:rooms) do
      add :name, :string
      add :image, :binary
      add :house_id, references(:houses, on_delete: :nothing)

      timestamps(type: :utc_datetime)
    end

    create index(:rooms, [:house_id])
  end

Are the following router.ex configuration OK? fulfilled the right URL structure ?

#Houses
live "/houses", HouseLive.Index, :index
live "/houses/new", HouseLive.Index, :new
live "/houses/:id/edit", HouseLive.Index, :edit
live "/houses/:id", HouseLive.Show, :show
live "/houses/:id/show/edit", HouseLive.Show, :edit
#Rooms
live "/houses/:house_id/rooms", RoomLive.Index, :index
live "/houses/:house_id/rooms/new", RoomLive.Index, :new
live "/houses/:house_id/rooms/:id/edit", RoomLive.Index, :edit
live "/houses/:house_id/rooms/:id", RoomLive.Show, :show
live "/houses/:house_id/rooms/:id/show/edit", RoomLive.Show, :edit

Any suggestion or comment will be welcome I just keep learning and following in love with the language and the framework. Thanks in advance

This is really a matter of taste though there are at least a couple of things to consider.

If you have breadcrumbs, then having houses/:house_id/rooms/:room_id can certainly be useful. I personally really dislike this structure though. First, if the room were to change to a new house, its URL changes, and second, if I happen to know the id of a room and want to just type the URL in my address bar, I now need to go find its house id as well. Both problems could be solve by having rooms/:id redirect to houses/:house_id/rooms/:room_id, though that’s a little heavy-handed if you don’t strictly need it. And of course if you have a lot of routes where having them both at the top level becomes unmanageable, that’s a different story as well.

This one seems unusual - how is it different from /houses/:id/edit?

This style of routes isn’t universally popular. The main reason is that the URL is repeating itself - there’s only one valid :house_id that goes with a specific room’s :id.

That’s not always undesirable - for instance, if you wanted to have a “secret URL” feature where the house ID is the hard-to-guess bit - but it’s usually not necessary.

Compare URLs like /houses/:house_id/rooms/new, where the house_id is critical to interpreting the request

Hello guys a lot of thanks for your answers. I’m gonna try to do my best to give you a context in order to try to explain my approach.

@sodapopcan has mentioned something for me is important
“First, if the room were to change to a new house, its URL changes, and second, if I happen to know the id of a room and want to just type the URL in my address bar, I now need to go find its house id as well”

That’s why I wrote the relationship is a composition so the room has a strong relationship with the house so it can not live without the house, I mean is not transferable.

Part of my doubts with my approach is I need to show all the rooms for an specific house and the only way that came to my mind was that in order to get the house_id from the URL and also use it for rooms list, creation. In fact I started doing with rooms/, rooms/new, rooms/:id and so on but then I started to wonder if I was doing in the right way.

@al2o3cr that’s a good question
" This one seems unusual - how is it different from /houses/:id/edit ?"

I just used mix phx.gen.live for creating the context and liveviews and then I just followed the documentation example https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Live.html

Also you have a good point what about hide the id’s like house_id. Currently I’m clueless and have no idea how to do it with secret’s URL. do you have an example I could follow because it could be very important in a professional WEB APP.

There is another thing I want to share, I’m pretty sure you are gonna guide me for the right way.
The way I have taken for get the house_id param from the URL I think is not too elegant or at least I don´t feel too confortable so far mainly because I get warnings for getting data from _params and also because I extract the value to pass it again in the socket ( I think this is the right choice but as I said at the beginning I’m learning and there are a lot of things I not too confident yet)

the underscored variable “_params” is used after being set. A leading underscore indicates that the value of the variable should be ignored. If this is intended please rename the variable to remove the underscore

These are fragments of my room_live/index.ex file (btw I used the same context for Houses and Rooms and I called Houses)

def mount(_params, _session, socket) do
    house_id = _params["house_id"]
    {:ok, stream(socket, :rooms, Houses.list_rooms_by_house(%House{id: house_id}))}
  end

....

defp apply_action(socket, :new, _params) do
    house_id = _params["house_id"]
    socket
    |> assign(:page_title, "New Room")
    |> assign(:room, %Room{})
    |> assign(:house_id, house_id)
  end

  defp apply_action(socket, :index, _params) do
    house_id = _params["house_id"]
    socket
    |> assign(:page_title, "Listing Rooms")
    |> assign(:rooms, Houses.list_rooms_by_house(%House{id: house_id}))
    |> assign(:house_id, house_id)
  end

Then I use the house_id to include it as a hidden field of the form and then at the creation I can keep the relationship between houses and the rooms belongs to it.

Thanks in advance for all the advices you can give me

Yes, just remove the underscore. As the warning said, an underscore indicates you will not use the variable. It is a little bit more than a convention since you get warnings, but as you can see _params actually still does bind the variable. On the other hand, a _ by itself actually throws the variable away—trying to reference _ won’t work. But it’s convention to mostly name your throw-away variables so it’s clear what they are.

As far as list_rooms_by_house goes, if you want to list by house_id, you aren’t gaining anything by putting the into a %House{} struct like that. If you want to first ensure the house exists then fetch the house from the database. There are differing opinion here but as you have it, I would just change the function to list_rooms_by_house_id(house_id). You could also fetch the house and preload the rooms.

def get_house_with_rooms(id) do
  House
  |> Repo.get(id)
  |> Repo.preload([:rooms])

What you do heavily depends on how your app works so it’s hard to go over everything. But, for instance, if you have users that can only access certain houses, then putting house_id in a hidden field is a security concern. You would want to first load the house and ensure the user is allow to read/write it (whatever the permissions may be). Then I would have a create_room(house, rooms_attrs) function (whether that lives in a Houses or Rooms context is up to you and a different discussion). Merely putting the house_id in a hidden field would allow a user to change that to whatever id they wanted. If that’s not a security concern then it’s no big deal, but if it is then you definitely shouldn’t do this.