Today, I want to show you how to start building a blog using Phoenix. You can find the source code here: https://github.com/jonathanyeong/phoenix_blog/tree/v0.0.1. We are using Phoenix version 1.5.3 and Elixir version 1.10.3. We will cover:
Project Setup
After installing Phoenix and Elixir (see Phoenix docs). Setup your new blog application.
mix phx.new phoenix_blog
cd phoenix_blog/
mix ecto.create
Generate migration
Firstly, we need to create the Post table. We can use the mix task phx.gen.schema
to generate a schema and a migration file (see the docs for more info). The schema is how we represent information from the database within our application. While, the migration contains the commands that run on the database itself.
mix phx.gen.schema Post posts content:text title:string
mix ecto.migrate
Add Routes
Now that we’ve run the migration, we need to add routes to our app so that we can view and create posts on the front-end. We will be using resources
to generate resourceful routes for us.
In lib/phoenix_blog_web/router.ex
scope "/", PhoenixBlogWeb do
resources "/posts", PostController
end
If you run mix phx.routes
. You should see:
post_path GET / PhoenixBlogWeb.PostController :index
post_path GET /posts PhoenixBlogWeb.PostController :index
post_path GET /posts/:id/edit PhoenixBlogWeb.PostController :edit
post_path GET /posts/new PhoenixBlogWeb.PostController :new
post_path GET /posts/:id PhoenixBlogWeb.PostController :show
post_path POST /posts PhoenixBlogWeb.PostController :create
post_path PATCH /posts/:id PhoenixBlogWeb.PostController :update
PUT /posts/:id PhoenixBlogWeb.PostController :update
post_path DELETE /posts/:id PhoenixBlogWeb.PostController :delete
Now if you start the Phoenix server
mix phx.server
And navigate to localhost:4000/posts
you will get an error. Let’s fix this.
Add the Post Controller
All of these routes point to a nonexistent Post Controller. Lets create one and add the following code.
# lib/phoenix_blog_web/controllers/post_controller.ex
defmodule PhoenixBlogWeb.PostController do
use PhoenixBlogWeb, :controller
alias PhoenixBlog.{
Post,
Repo
}
def index(conn, _params) do
posts = Post |> Post.ordered() |> Repo.all()
render(conn, "index.html", posts: posts)
end
def new(conn, _params) do
changeset = Post.changeset(%Post{}, %{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"post" => post_params} = _params) do
changeset = Post.changeset(%Post{}, post_params)
case Repo.insert(changeset) do
{:ok, _log} ->
conn
|> put_flash(:info, "Success - created a Post!")
|> redirect(to: Routes.post_path(conn, :index))
{:error, changeset} ->
conn
|> put_flash(:error, "failure couldn't create post")
|> render(:new, changeset: changeset)
end
end
end
Jump to Post View & Templates.
Breakdown
defmodule PhoenixBlogWeb.PostController do
...
end
Controllers are just modules in Elixir. The module name (PostController
) needs to match the filename post_controller.ex
.
use PhoenixBlogWeb, :controller
use
will require and inject the Phoenix.Controller
code and the Plug.Conn
into our Post Controller. It does this by calling the macro __using()__
. You can see the code injected here.
alias PhoenixBlog.{
Post,
Repo
}
Alias
is used to define shortcuts. Instead of writing PhoenixBlog.Post
we can instead write Post
.
def index(conn, _params) do
posts = Repo.all(Post)
render(conn, "index.html", posts: posts)
end
The index
method takes in a conn
and a params
argument. conn
comes from Plug.Conn. Which was injected above via use
. conn
is a module that defines low level functions such as request information. Next is params
, we will prepend it with _
to signify that we aren’t using it. We then call Repo.all(Post)
to pull all the posts from the database. These posts are assigned to a variable which we can access in the index view via @posts
.
def new(conn, _params) do
changeset = Post.changeset(%Post{}, %{})
render(conn, "new.html", changeset: changeset)
end
The new
method creates a changeset
that is passed to the new view. A changeset in Elixir will only allow data with specific information to go through. It will also complain if it expects data but doesn’t receive it. Post.changeset/2
method takes in a Post struct and some attributes. Since we’re creating a new Post, there are no attributes so instead we’ll pass in an empty map.
def create(conn, %{"post" => post_params} = _params) do
changeset = Post.changeset(%Post{}, post_params)
case Repo.insert(changeset) do
{:ok, _log} ->
conn
|> put_flash(:info, "Success - created a Post!")
|> redirect(to: Routes.post_path(conn, :index))
{:error, changeset} ->
conn
|> put_flash(:error, "failure couldn't create post")
|> render(:new, changeset: changeset)
end
end
The create method is pretty big so let’s break this down to smaller parts.
...%{"post" => post_params} = _params
The first line of the method uses Elixir’s pattern matching to assign anything found in params["post"]
to post_params
.
changeset = Post.changeset(%Post{}, post_params)
We’ve seen changeset
being used for the new
route. In the create
route we’re passing in the post_params
instead of an empty map. This creates a changeset that is populated by the form data. Which we’ll save to the database via:
Repo.insert(changeset)
Repo.insert/1
will return one of two tuples.
{:ok, _log} ->
conn
|> put_flash(:info, "Success - created a Post!")
|> redirect(to: Routes.post_path(conn, :index))
On a successful insert into the database, it will display a success flash message and redirect to the index page where all the posts will be displayed.
{:error, changeset} ->
conn
|> put_flash(:error, "failure couldn't create post")
|> render(:new, changeset: changeset)
On an error inserting into the database, it will display an error flash message and re-render new.html.eex
.
Add Post View & Templates
Phoenix’s has a strong naming convention, which will link templates in the post
directory and the view, post_view
, to the Post Controller.
Templates in Phoenix are written in eex
which stands for Embedded Elixir. They are the building blocks to a webpage. When we start the Phoenix server, templates get rendered into the view and displayed as html.
# lib/phoenix_blog_web/views/post_view.ex
defmodule PhoenixBlogWeb.PostView do
use PhoenixBlogWeb, :view
end
# lib/phoenix_blog_web/templates/index.html.eex.
<h1>Blog Post</h1>
<%= link "+ Add new post", to: Routes.post_path(@conn, :new) %>
<%= for post <- @posts do %>
<h2><%= post.title %></h2>
<p><%= post.content%></p>
<% end %>
# lib/phoenix_blog_web/templates/post/new.html.eex
<h1>New Blog Post</h1>
<%= form_for @changeset, Routes.post_path(@conn, :create), fn f -> %>
<label>
title:
</label>
<%= text_input f, :title %>
<label>
Content:
</label>
<%= textarea f, :content %>
<%= submit "Submit!" %>
<% end %>
Breakdown
# lib/phoenix_blog_web/views/post_view.ex
defmodule PhoenixBlogWeb.PostView do
use PhoenixBlogWeb, :view
end
The Post view has a similar structure to the Post controller. use
will inject the Phoenix.View code.
# lib/phoenix_blog_web/templates/index.html.eex.
<h1>Blog Post</h1>
<%= link "+ Add new post", to: Routes.post_path(@conn, :new) %>
<%= for post <- @posts do %>
<h2><%= post.title %></h2>
<p><%= post.content%></p>
<% end %>
The index view will add a link that routes to the new Post path. It will also loop through all the posts
and display the title and content. The @posts
variable was set in the PostController index method.
# lib/phoenix_blog_web/templates/post/new.html.eex
<h1>New Blog Post</h1>
<%= form_for @changeset, Routes.post_path(@conn, :create), fn f -> %>
<label>
title:
</label>
<%= text_input f, :title %>
<label>
Content:
</label>
<%= textarea f, :content %>
<%= submit "Submit!" %>
<% end %>
The form inside new.html
will use the changeset
variable we set in the PostController new method. If you want more information on the types of input fields, see the Phoenix docs. When the user clicks on submit, the form data gets sent to the PostController create method via Routes.post_path(@conn, :create)
.
Conclusion
Phew, we finally made it! Now if you start your webserver:
mix phx.server
Navigate to localhost:4000/posts
you should see Blog Post
as a header. If you click on the link or go to localhost:4000/posts/new
you should see a form to create a post.
Try implementing the update
and edit
route next! You can apply the patterns in new
and create
to help guide you. I hope this tutorial has highlighted how easy it is to get started with Phoenix. If you enjoyed this article please leave a comment below. Happy coding!
Top comments (2)
Awesome. I really like how it feels so close to Ruby on Rails.
Me too! It's definitely made the learning curve easier. I do think Phoenix feels snappier than Rails.