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
andemail
- 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!