Back

How to Create a Basic Web App With the Phoenix Framework

Overview

In this blog post I’m going to be teaching you how to build a basic web app with the Phoenix 1.2 framework. I will also describe neat little tricks I’ve learned along the way. I will be creating a nested model relationship app and picking up from where the getting started guide for phoenix leaves off. If you haven’t gone through the getting started guide for elixir and are feeling a little lost please consult the Phoenix Framework Overview page.

This will be a simple app that allows you to create users and then create cars for that user. Simple, right? Let’s go! You can see the code here.

 

Setup

Things you’ll need:

  • Postgres: brew install postgresql. This will be your DB service.
  • Node & NPM: brew install node. You’ll want NPM for all the JS goodies you get with Phoenix. Phoenix uses Brunch.io.
  • Elixir: http://elixir-lang.org/install.html
  • Phoenix:
    • $ mix local.hex
    • $ mix archive.install https://github.com/phoenixframework/phoenix/releases/download/v0.12.0/phoenix_new-0.12.0.ez

Alright let’s create our simple app.

 

Create a new Phoenix app

First we’ll create our app with the mix phoenix.new command.

$ mix phoenix.new simple_phoenix_app

When it asks:

  • Install mix dependencies? [Yn]
    • yes.
  • Install brunch.io dependencies? [Yn]
    • yes.

Now you should have all your dependencies to create the app.

That’s going to install all the things you need for your Phoenix app. I had never heard of Brunch before Phoenix, but it’s a really nice template generator. Check it out here: (http://brunch.io/)

Okay, now we have our new Phoenix project. Let’s run it.[/vc_column_text][vc_column_text]

$ cd simple_phoenix_app$ mix phoenix.server

Now you should be able to open your browser and visit http://localhost:4000

Just like you would see in Rails, it’s a simple welcome page. Yay!

Okay, now it’s time to show you the coolest “batteries included” feature about this framework. Open the simple_phoenix_app/web/templates/page/index.html.eex file in your editor. Make sure you have the editor and browser pointed at http://localhost:4000 and that it is visible. Now, at the top of the page add a h1 to the jumbotron div. like:

<div class="jumbotron">  <h1>O Snap!</h1>  <h2>Welcome to Phoenix!</h2>  <p class="lead">Most frameworks make you choose between speed and a productive environment. <a href="http://phoenixframework.org">Phoenix</a> and <a href="http://elixir-lang.org">Elixir</a> give you both.</p></div>

Then hit save. OMG! Did you catch that?! Auto-reloading baked in!

 

Create user scaffolding

Alright, let’s create our first model. We’ll use the scaffold like command for Phoenix.

$ mix phoenix.gen.html User users name email

You can see what the command above is doing by the output:

  • A User model
  • A users table
  • With the string attributes name and email
  • The controller, view, and templates needed for requests
  • And some tests for you.

Now let’s follow the directions from the output.

Add resources "/users", UserController to the router.ex file.

### simple_phoenix_app/web/router.exscope "/", SimplePhoenixApp do  pipe_through :browser # Use the default browser stack  get "/", PageController, :index  resources "/users", UserControllerend

Before we migrate the DB, we’ll edit the DB connection info. You can leave it if you have a postgres user with the password set to postgres. Since I don’t, I’m going to change mine to my username. If you change this setting be sure to restart your server. If you don’t, it won’t pick up the changes.

### simple_phoenix_app/config/dev.exs...# Configure your databaseconfig :simple_phoenix_app, SimplePhoenixApp.Repo,  adapter: Ecto.Adapters.Postgres,  username: "meatherly",  database: "simple_phoenix_app_dev"

Now let’s create the DB and Migrate.

$ mix ecto.create$ mix ecto.migrate

Then let’s add a link to our home page that points to the user’s index route.

<!-- simple_phoenix_app/web/templates/page/index.html.eex --><div class="jumbotron">  <h2>Welcome to Phoenix!</h2>  <p class="lead">Most frameworks make you choose between speed and a productive environment.</p>  <%= link "Users", to: user_path(@conn, :index) %></div>

Now you should be able to click on the user’s link on the home page. I didn’t have to refresh the page either 🙂

Boom! We have our basic user index page!

You can go ahead and create a user and all that fun stuff. Try not to let your face melt with the speed.

Create car model

Now let’s create our Car model for our users. We’ll use the model generator because we’re not creating a top level route.

$ mix phoenix.gen.model Car cars name year:integer

We need to edit the migration first to make the referenced foreign key for the users and cars. To do that we’ll need to add the references function to the user_id column.

### simple_phoenix_app/priv/repo/migrations/*_create_car.exsdefmodule SimplePhoenixApp.Repo.Migrations.CreateCar do  use Ecto.Migration  def change do    create table(:cars) do      add :user_id, references(:users)      add :name, :string      add :year, :integer      timestamps    end  endend

Now let’s migrate our DB to add the latest migration.

$ mix ecto.migrate

 

Adding a relationship between cars and users

We’ll need to edit our car and user model and add the relationships to it.

### simple_phoenix_app/web/models/user.ex###...schema "users" do  has_many :cars, SimplePhoenixApp.Car  field :name, :string  field :email, :string  timestampsend###...

 

### simple_phoenix_app/web/models/car.ex###...schema "cars" do  belongs_to :user, SimplePhoenixApp.User  field :name, :string  field :year, :integer  timestampsend###...

 

Adding Car controller, view and templates

Now let’s create our Controller, View, and Templates.

Controller

### simple_phoenix_app/web/controllers/car_controller.exdefmodule SimplePhoenixApp.CarController do  use SimplePhoenixApp.Web, :controller  alias SimplePhoenixApp.Car  plug :actionend

View

### simple_phoenix_app/web/views/car_view.exdefmodule SimplePhoenixApp.CarView do  use SimplePhoenixApp.Web, :viewend

Templates

simple_phoenix_app/web/templates/index.html.eex

<h2>Listing cars for <%= @user.name %></h2><table class="table">  <thead>    <tr>      <th>Name</th>      <th>Year</th>    </tr>  </thead>  <tbody><%= for car <- @cars do %>    <tr>      <td><%= car.name %></td>      <td><%= car.email %></td>    </tr><% end %>  </tbody></table><%= link "New car", to: user_car_path(@conn, :new, @user) %>

simple_phoenix_app/web/templates/new.html.eex

<h2>New car for <%= @user.name %></h2><%= render "form.html", changeset: @changeset,                        action: user_car_path(@conn, :create, @user) %><%= link "Back to cars", to: user_car_path(@conn, :index, @user) %>

simple_phoenix_app/web/templates/form.html.eex

<%= form_for @changeset, @action, fn f -> %>  <%= if f.errors != [] do %>    <div class="alert alert-danger">      <p>Oops, something went wrong! Please check the errors below:</p>      <ul>        <%= for {attr, message} <- f.errors do %>          <li><%= humanize(attr) %> <%= message %></li>        <% end %>      </ul>    </div>  <% end %>  <div class="form-group">    <label>Name</label>    <%= text_input f, :name, class: "form-control" %>  </div>  <div class="form-group">    <label>Year</label>    <%= number_input f, :year, class: "form-control" %>  </div>  <div class="form-group">    <%= submit "Submit", class: "btn btn-primary" %>  </div><% end %>

And one last thing. Let’s add our car’s route to the router.ex file. Just add the cars route inside a block for the users route.

scope "/", SimplePhoenixApp do  pipe_through :browser # Use the default browser stack  get "/", PageController, :index  resources "/users", UserController do    resources "/cars", CarController  endend

Alright, time to edit our CarController to add the needed plugs and actions. Let’s start with a plug to find the user. Add the find_user function at the bottom of the controller. Then add the plug :find_user above the action plug since plugs are executed in the order they are defined.

defmodule SimplePhoenixApp.CarController do  #...  plug :find_user  plug :action  #...  defp find_user(conn, _) do    user = Repo.get(SimplePhoenixApp.User, conn.params["user_id"])    assign(conn, :user, user)  endend

Now let’s add our index action to the controller

def index(conn, _params) do  user = conn.assigns.user  cars = Repo.all assoc(user, :cars)  render conn, cars: cars, user: userend

You’ll notice we get our user from the conn.assigns map that we made down in the find_user function. You can’t really have instance variables flying around your controllers (like you can in Ruby) in Elixir. So, we have to add assignments to the connection and pass that around.

Next, let’s add a link to the user’s cars on the user’s show page.

<!-- simple_phoenix_app/web/templates/user/show.html.eex --><%= link "Back", to: user_path(@conn, :index) %>| <%= link "Cars", to: user_car_path(@conn, :index, @user) %>

You can add this link to the bottom of the show page.

Now we will create a new user on our web app using the site. Navigate to http://localhost:4000/users and click on the New User link.

Create the new user. Submit the form and you should see your user. Click on the show button for that user and you should see the Cars link we made on the bottom of the page. YAY!

Add new action for car controller

Now we will add the new action to the car controller to render the new car form. Since we’ve made that find_user plug, it’s going to be smaller than you think 🙂

#...def new(conn, _) do  changeset = Car.changeset(%Car{})  render conn, changeset: changesetend#...

You might be wondering, “won’t I need the user for the view since we have a @user variable in the view?” Well, no. Since we’ve added the user to the connections assignments that means it was sent down with the connection to the view and the view passed it along to the templates.

Pro tip: You can add things to the assigns to DRY up your controllers.

Okay, now you can render the new car form from the car index page. YAY!

 

Add create action for car controller

Now let’s add the create action so we can submit the car form. This one will be a bit longer than the new action.

def create(conn, %{"car" => car_params}) do  changeset =    build(conn.assigns.user, :cars)    |> Car.changeset(car_params)  if changeset.valid? do    Repo.insert(changeset)    conn      |> put_flash(:info, "Car has been successfully created.")      |> redirect(to: user_car_path(conn, :index, conn.assigns.user))  else    render(conn, "new.html", changeset: changeset)  endend

“Wow!” right? Let’s talk about some of this. First off, let’s talk about that random build function just hanging out in there. I stumbled into this file: simple_phoenix_app/web/web.ex and saw where all the use SimplePhoenixApp.Web, :view are coming from. It has all the View, Model, Controller, and Router functions defined. In the controller one they import Ecto.Model, which has the build function. It’s very similar to the ActiveRecord build method. You can read more about it here. The rest of the action should look very similar to the UserController create action. So if the changeset isn’t valid, we’re just rendering the new page again with the errors. Else we’re sending them back the index page for the cars for the user.

Well, what are you waiting for? Try it out!

 

Extra credit

After talking with some guys in the IRC channel, I found out that I can DRY up our controller by adding the alias SimplePhoenixApp.User to the simple_phoenix_app/web/web.ex file in the controller section. Let’s do that for kicks!

def controller do  quote do    use Phoenix.Controller    # Alias the data repository and import query/model functions    alias SimplePhoenixApp.Repo    import Ecto.Model    import Ecto.Query, only: [from: 2]    # Import URL helpers from the router    import SimplePhoenixApp.Router.Helpers    alias SimplePhoenixApp.User  endend

Now we can take out the long namespaced names for User in the CarController. Yay!

 

Ending notes

So how ’bout dem apples? We’ve built a simple Phoenix App! I bet you have a lot questions and I bet I don’t have all the answers. If you head over to the IRC room (#elixir-lang), there are some great guys in there that would love to help; even the creator of the Framework! 🙂

9-6-17: Edited to add that this is for Phoenix 1.2 and not Phoenix 1.3. Thanks  Declan for keeping us on our toes!

 

 

Michael Eatherly
Michael Eatherly