Where is a Phoenix app's 'main' entry-point function?

Hello all,

Usually when I write a program in another language like Python, there’s a clear-cut way to start something without having to manually execute a function from the command line (mix run -e Module.function()). That’s because, from the way I see it, there’s ‘main’ entry-point function from which the program launches, and other functions get run from there.

I can’t find any article that simply points to Phoenix’s main entry-point function from which I can simply execute a simple function (My.Stuff.populate_database()) without having to write some Agent/GenServer module and Supervising children? I get article’s like “What’s the main use of Phoenix”, but nothing that points to an entry-point of the program from which I can just run a function.

I need help. Where is Elixir/Phoenix’s ‘main’ function or its equivalent which which I can run a simple function? Thank you.

2 Likes

The :phoenix application has it’s application start callback in Phoenix.

But you can not really call anything from there, that code has already been written.

And even though you can reload modules at will, the application has probably already been started.

Maybe explain a bit better what you are trying to achieve.

3 Likes

I am experimenting with Ecto and I have a module called Custom.DB that creates an in-memory database.

I have included the module as one of the children in application.ex.

However, before I can use it, I have to first create the tables, and populate the database. I’ve created a function for doing so, but I don’t know how to get it to run when the program starts with iex -S mix phx.server.

defmodule Custom.DB do
  use Ecto.Repo,
    otp_app: :testing,
    adapter: Ecto.Adapters.SQLite3

  def create_tables do
    :testing
    |> :code.priv_dir()
    |> Path.join("repo/testing.sql")
    |> File.read!()
    |> query()
  end

  def show_tables do
    query("select * from sqlite_schema;")
  end
end

in application.ex:

    children = [
    ...

      {Custom.DB, database: :memory, pool_size: 1}
      ...
    ]

Where do I run Custom.DB.create_tables()?

1 Like

There’s no single entrypoint on the beam, which you can customize. That’s not how the abstractions are setup. The beam when starting will start a set of application. Each application can optionally provide a callback module, which (among others) will have the beam execute the start/2 function of that application. That’s where you put code to execute on startup.

You’re already have that callback module and function, because that’s where the children list is defined, which you posted.

7 Likes

Mhmm… thank you for trying, but I still don’t get it. Maybe my question wasn’t specific enough.

My question is: Where do I run Custom.DB.create_tables() so my database is populated with whatever I have in ‘repo/testing.sql’ when I run iex -S mix phx.server?

1 Like

That totally depends on your exact setup, though I would probably do that kind of seeding in the process that owns the database, such that it’s reseeded if the process ever has to be restarted.

2 Likes

Do you want to do that whenever your application starts, only in development, only when using the phx.server mix task, …. There’s a lot of versions to this question, which do not share their answer.

3 Likes

I see. So there’s a lot more to this.

I want to have this run whenever my application starts. So, Custom.DB.create_tables() should be executed everytime I start the server, in dev or prod.

Once the database is populated, I can render views using the data within wih Ecto (Repo, Query, Schema).

1 Like

Then the start/2 callback of your application module (usually MyApp.Application) is the place to run such things.

2 Likes

I really think the owning process should be responsible, not the application, to ensure that a crash in the DB process at least recovers into “empty but usable” database, rather than a database that doesn’t even have a schema.

1 Like

Maybe. Sometimes that additional level of rigor is worth it. But I’d argue that’s step two. It’s no longer just the question of where the beams “main” is.

4 Likes

I mean, where do I type Custom.DB.create_tables()? Inside here?

  def start(_type, _args) do
  end

I’ve tried putting it before and after Supervisor.start_link(children, opts), and in both cases, the program just crashes.

1 Like

So when start/2 is called your application is just beginning to start up. Your supervision tree is not yet started, which includes your repo. So calling Custom.DB.create_tables() will fail. You could run the function inside the supervision tree as well, after your repo is initialized (more flexible option) or you could run it after Supervisor.start_link, but you need to ensure to still return the result of that call from start/2. start/2 is expected to return a value according to the Application behaviour (Application — Elixir v1.19.5) and commonly that would be the result of starting a supervisor. This is to ensure the application (and often the whole vm) stops when the root process exits.

2 Likes

Mmmkay. So, I don’t know if what I’m doing now is considered best practice, but this seems to get me the result I want:

 def start(_type, _args) do
...
     response = Supervisor.start_link(children, opts)
      Custom.DB.create_tables()
    response

end

1 Like

It’s not necessarily best practice, but also not too out of the ordinary. The difference is mostly in the effects you get if your create_tables fails to work somewhere. How you want to handle that is a different question.

1 Like

Okay. Could you point me in the right direction so I can explore what NobbZ meant by the “owning process should be responsible”? I can understand that if the owning process fails, the database will be empty when it’s restarted with my current ‘solution’. What function must I write in Custom.DB to achieve that?

1 Like

This piece of the Elixir docs talks about applications and their life cycle:

Worth knowing the underlying concept comes from Erlang, so to understand why things are the way they are you can also look into Erlang/OTP docs.

This explains the start callback:

For a custom Ecto.Repo, look at

This is the implementation of that callback when you write use Ecto.Repo in your custom module:

AFAIR there’s no “run this code on startup” hook out of the box in Ecto.Repo, but generally many possible entry points with different consequences as the other knowledgeable answers discuss.

I may be sending you off on the wrong direction as I don’t recall having to do it myself, but you can always wrap the Repo with a module you fully control and write the initialization code there, so that your supervision tree will be like MyApp -> MyApp.MyRepoInit -> MyApp.Repo.

8 Likes

Thanks for that. I think my problem is my mind is stuck in my old way of thinking of programming. Obviously, Elixir, Erlang, BEAM, OTP, is a completely different tool that my mind isn’t accustomed to yet.

So, now this is my current solution:

defmodule Custom.DB do
  use Ecto.Repo,
    otp_app: :testing,
    adapter: Ecto.Adapters.SQLite3

  def create_tables do
    :testing
    |> :code.priv_dir()
    |> Path.join("repo/testing.sql")
    |> File.read!()
    |> query()
  end

  def show_tables do
    query("select * from sqlite_schema;")
  end
end


defmodule Custom.DB.Init do
  def start_link(opts \\ []) do
    result = Custom.DB.start_link(opts)
    Custom.DB.create_tables()
    result
  end

  def child_spec(opts) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, [opts]},
      type: :worker,
      restart: :permanent,
      shutdown: 500
    }
  end

end

and in application.ex:

    children = [
    ...

      {Custom.DB.Init, database: :memory, pool_size: 1}
      ...
    ]

That’s the best I’ve got. :joy:

2 Likes

It’s not that it’s completely different. The application start is pretty analogous to a main() function in another language. The difference is just that OTP has a bunch of abstractions layered on top of that for supervising processes because that’s what it’s designed for.

You could imagine inventing a similar abstraction in Python. Creating a DBInit class that has a run() method which creates the database. And then having some code like

def main():
  children = [
    DBInit,
    StartServer,
  ]
  for child in children:
    child().run()

It’s the same idea except we have process isolation, which is very powerful.

3 Likes

You demonstrate the curiosity to learn and figure out the new ways, you can do it!

If you’re into video content, there is a lot of videos on YouTube that can help you get a better sense of the mental model of Erlang and the BEAM, which trickle down to Elixir (and probably other BEAM languages as well).

In particular, I love the talks from Joe Armstrong, one of Erlang’s creators.

Funny thing is that Python doesn’t have a specific entry point like C or Go (func main), but will interpret all files passed to the interpreter, so it’s up to you to do something like:

if __name__ == "__main__":
    main() # or any other code here

Where __main__ is a magic variable provided by the interpreter that will have the name of the current module when imported, or the string __main__for the top-level module (so the same code can observe different values for that variable depending how it was run)… but maybe that’s material for a Python forum :slight_smile:

4 Likes