Question about Phoenix Directory Structure

The Phoenix Framework Doc shows the Directory Structure like this:

lib/hello_web
├── controllers
│   ├── page_controller.ex
│   ├── page_html.ex
│   ├── error_html.ex
│   ├── error_json.ex
│   └── page_html
│       └── home.html.heex
├── components
│   ├── core_components.ex
│   ├── layouts.ex
│   └── layouts
│       ├── app.html.heex
│       └── root.html.heex
├── endpoint.ex
├── gettext.ex
├── router.ex
└── telemetry.ex

I have some question about this:

  1. Now we have just one page controller, inside controllers directory we have 2 page_* files and 1 page_* directory, if we also add JSON support, then one more file, one controller = 3 files + 1 directory, if we have 10 controllers, will we have 30 files + 10 directories in controllers directory?
  2. If we also need email, like in Rails, where should we put its directory, do we have some generator? And how about jobs?

I ask these questions because I just come from Rails, Phoenix looks simple but also brought too much confuse.

I truely hope Phoenix world have a book like 《 Agile Web Development with Rails 7》, The official document is good, but lack of focus and best practices.

1 Like

Phoenix is not opinionated like Rails. There is a tiny bit of “magic” happening with via some dabs of reflection but it’s not pervasive. Phoenix doesn’t care at all where you put your files and the generated structure is more of a guideline. In fact, Phoenix is arguably closer to Sinatra than it is to Rails with $ mix phx.new being what makes it a full fullstack framework. There are single file examples of Phoenix apps out there!

I also came from Rails so I definitely shared some of your pain. I was particularly perturbed that many examples don’t show you exactly were to put files. I’m not saying this is better or worse, but you get used to it pretty quickly.

Your example is actually slightly flawed when comparing number of files to Rails. In Rails, the equivalent page_ files you mentioned would be controllers/page_controller.rb and views/pages/show.html.erb and helpers/page_helper.rb, so actually 3 files and 3 directories! The _html files in Phoenix are a new concept but basically they used to be called views. Phoenix splits up the concept of “view” into “html” and “template” (it used to be “view” and “template”) where html compiles the templates and controllers render the html. This is actually closer to how the general concept of a view quote-unquote “should” work (the “view” is the presentation logic whereas the “template” is the static markup) whereas Rails simplifies it by combining view+template then optionally allowing a ‘helper’ file if you need added presentation logic. In Phoenix, you would put your “helpers” in the PageHTML module. So, you are right in that Phoenix always forces you to have three modules (er, two modules + one template). If you’re strictly concerned about number of files and you don’t mind putting your templates in functions, you can actually define your templates right in PageHTML then you don’t need the show.html.heex file:

defmodule MyAppWeb.PageHTML do
  # ...
  def show(assigns) do
    ~H"""
    <h1>I'm the show page</h1>
    """
  end
end

I actually don’t use the default directory structure at all for web. I don’t have many controllers as I often have LiveViews but I break up my Web module into site sections, so I don’t have a controllers or live directory. Mine would look like this:

my_app_web/
  page/
    page_controller.ex
    page_html.ex
    html/
      show.html.heex

You don’t have to do anything special to make this work—just move the files around. Although you’ll have to likely have to change the glob given to PageHTML.embed_templates.

4 Likes

Thank you so much for your explanation, I feel much clearer now. Yes, this is really what makes me feel confuse. In rails, if you don’t put things in the right place, maybe it won’t work.

In other side, you are also right that Rails has 3 directories + 3 files, It’s just seems Rails put them in a “comfortable” place, but in Phoenix we are all in “controllers”, I just cannot imagine what will it looks like if it were a large app. That’s why.

1 Like

The reason it’s nested under controllers is that it mimics the shape of what is going on. It’s a more uniform, single-direction relationship of controller → html → template whereas in Rails it’s controller → view with a helper on the side (and I’m willing to be you’ve worked on Rails projects that abuse helper_method to pull helper methods into the controller as well). It arguably encourages a cleaner design as your app grows. You could even make it look like Rails if you wanted:

my_app_web/
  controllers/
    page_controller.ex
  views/
    pages/
      show.html.ex
  helpers/
    page_html.ex

I would not recommend this, though :sweat_smile:

Personally, I wouldn’t be upset if Phoenix were more opinionated. It certainly helps with learning quicker. My biggest hurdle at the time I learned Rails was organization so Rails was a godsend! However, I worked on a massive Rails app (well over 300 models if that is any kind of measure) and organization becomes very hard there. Decisions were made and a good solution was found, but you end up fighting the framework in that case. With Phoenix there would be far less friction when it comes to re-organizing a massive app like that. Using ActiveRecord in the RailsWay™ actually became a problem at that scale as well… turns out having 300 has_one/has_manys in the User model wasn’t too scalable :sweat_smile: although that is getting off topic.

EDIT: Oops, I meant include helpers into the controller! I got helper_method backwards :man_facepalming:

1 Like

I can imagine what it will looks like if 300 models in a Rails project, or 300 has_many in the User model. I worked on a Rails project which has 20+ has_many and I already feel my brain not enough.

But can you a bit explain why with Phoenix there would be far less friction?

It’s really just because the framework isn’t concerned about where your files are. For example, our Rails app was split up by feature so we ended up with many directories that essentially each had a mini Rails apps in them. It’s not the biggest deal in to make this work but you’re still going against the framework conventions. You also lose the social friction of new people coming into the app expecting it to look like a Rails app and it doesn’t. Phoenix doesn’t set up any such expectation. I’m not really saying it’s a massive, life-changing improvement but it is part of what drew me to Phoenix even if I do miss some stuff about Rails.

As far as Ecto vs ActiveRecord goes, I would say that is a massive improvement. It takes a bit to get comfortable with the shift away from an ORM, but Ecto is essentially like a super powerful and better designed arel. You don’t run into the same scaling issues with it since it just returns structs (which are no heavier than bare maps) as opposed to massive objects with methods spanning at least five different concerns.

5 Likes

Thank you so much again, I think I learn a lot from your answers, I will dive into Phoenix to feel the feeling you feel.

Also, one of the most happy thing in Elixir land is because we have so kindly people like you :heart:

3 Likes