How to Build a Simple, Extensible Blog with Elixir and Phoenix
Web development is an ever-evolving landscape with new frameworks and languages constantly emerging to improve the way we build applications. Two exciting technologies that have gained popularity in recent years are Elixir and Phoenix.
Elixir is a functional, dynamically-typed programming language that runs on the Erlang virtual machine. It was designed to be highly concurrent, fault-tolerant, and scalable. Phoenix is a web framework built with Elixir that follows the server-side MVC pattern and takes inspiration from established frameworks like Ruby on Rails.
In this guide, we‘ll walk through building a fully-featured blog application from scratch using Elixir and Phoenix. We‘ll cover everything from initial project setup to implementing a complete set of features you‘d expect to find in a modern blog. By the end, you‘ll have a solid foundation for creating your own Elixir and Phoenix applications.
Why Choose Elixir and Phoenix?
Before we dive into building our blog, let‘s take a moment to discuss what makes Elixir and Phoenix a compelling choice for web development.
Elixir runs on the battle-tested Erlang VM, which powers large applications with extremely high uptime requirements like WhatsApp and Ericsson‘s telecom switches. Thanks to its Erlang roots, Elixir is built to handle large numbers of simultaneous connections with minimal resource overhead. Phoenix takes full advantage of this capability with an architecture that handles hundreds of thousands of concurrent connections on a single server.
Elixir is also designed to be an extremely pleasant and productive language for developers. It has a clean, readable syntax and includes powerful features like pattern matching, pipelines, and protocols. Combined with an excellent standard library and a friendly community, Elixir makes it easy to write expressive, maintainable code.
Lastly, Phoenix provides an excellent development experience right out of the box. It offers a set of generators to quickly scaffold different parts of your application. It also comes with sensible defaults and follows conventions to help keep your codebase organized as it grows. And with first-class support for real-time functionality via WebSockets, Phoenix makes it simple to add engaging, dynamic user experiences to your applications.
Setting Up a New Phoenix Project
With the benefits of Elixir and Phoenix in mind, let‘s start building our blog application. The first step is to generate a new Phoenix project. Make sure you have Elixir and Phoenix installed, then run the following command in your terminal:
mix phx.new blog
This will generate a new Phoenix project in a directory called blog
and install the necessary dependencies. Follow the prompts to change into the project directory and set up the database:
cd blog
mix ecto.setup
Now we‘re ready to start building our application. Let‘s begin by creating the schema for our blog posts.
Defining the Post Schema
Phoenix uses Ecto for working with databases. Ecto is a powerful library that allows us to interact with different database backends using a consistent API. It also provides a way to define schemas that represent the structure of our data.
For our blog, we‘ll define a Post
schema that represents an individual blog post. Run the following command to generate the schema and associated database migration:
mix phx.gen.schema Blog.Post posts title:string body:text published_at:utc_datetime
This will generate a migration file in priv/repo/migrations
and a schema file in lib/blog/post.ex
. The migration file contains instructions for creating the posts
table, and the schema file defines the structure of a Post
with title
, body
, and published_at
fields.
Open up the migration file and modify it to add an index on the published_at
column:
# priv/repo/migrations/*_create_posts.exs
defmodule Blog.Repo.Migrations.CreatePosts do
use Ecto.Migration
def change do
create table(:posts) do
add :title, :string
add :body, :text
add :published_at, :utc_datetime
timestamps()
end
create index(:posts, [:published_at])
end
end
This will allow us to efficiently query for posts by their publication date. With our migration defined, we can run it to create the posts
table:
mix ecto.migrate
Implementing CRUD Actions
Now that we have our Post
schema defined, let‘s implement the basic CRUD (Create, Read, Update, Delete) actions. We‘ll start by generating a new controller:
mix phx.gen.html Blog Post posts title:string body:text
This command generates a number of files related to posts, including the controller, view, and templates. It also updates our router to include the new routes for posts.
Open up the generated controller file lib/blog_web/controllers/post_controller.ex
and update the index
action to only return published posts:
# lib/blog_web/controllers/post_controller.ex
def index(conn, _params) do
posts = Blog.list_posts(published: true)
render(conn, "index.html", posts: posts)
end
Then open the Blog
context in lib/blog.ex
and update the list_posts
function to accept a keyword list of options:
# lib/blog.ex
def list_posts(opts \\ []) do
query = from p in Post, order_by: [desc: p.published_at]
query = if Keyword.get(opts, :published, false), do: where(query, [p], not is_nil(p.published_at)), else: query
Repo.all(query)
end
This function uses Ecto‘s query syntax to retrieve all posts ordered by published_at
in descending order. If the :published
option is true
, it filters the results to only include posts with a published_at
date.
The other controller actions generated by Phoenix will work as is for now. We can verify everything is working by starting the Phoenix server with mix phx.server
and visiting http://localhost:4000/posts
in the browser. We should see a list of any posts we‘ve created, with buttons to show, edit, or delete each post.
Adding Admin Authentication
So far we‘ve set up basic CRUD functionality that is completely open to the public. For a real blog, we would want some authentication to ensure only authorized users can create, edit, or delete posts. Let‘s add an authentication system so we can restrict access to certain actions.
There are a few different libraries we could use to add authentication to our Phoenix application. For this example, we‘ll use the Pow library. Let‘s add it to our application by running:
mix pow.install
This will generate a migration to create the users
table and some view files for registration and session handling. Run the migration with:
mix ecto.migrate
Now we have a User
schema and the ability for users to register and log in. The next step is to restrict the post creation, editing, and deletion to authenticated users. Update the index
, new
, edit
, and delete
actions in the PostController
to require authentication:
# lib/blog_web/controllers/post_controller.ex
plug :authorize_user when action in [:new, :create, :edit, :update, :delete]
def index(conn, _params) do
posts = Blog.list_posts(published: true)
render(conn, "index.html", posts: posts)
end
defp authorize_user(conn, _) do
if Pow.Plug.current_user(conn) do
conn
else
conn
|> put_flash(:error, "You must be logged in to access that page.")
|> redirect(to: Routes.post_path(conn, :index))
|> halt()
end
end
The authorize_user
plug will run before the specified actions and check if there is a currently authenticated user. If not, it will redirect back to the post index with an error message.
We also need to restrict routes for unauthenticated users by adding a pipeline to the router:
# lib/blog_web/router.ex
pipeline :protected do
plug Pow.Plug.RequireAuthenticated,
error_handler: Pow.Phoenix.PlugErrorHandler
end
scope "/posts", BlogWeb do
pipe_through [:browser, :protected]
resources "/", PostController, except: [:index, :show]
end
This ensures that all routes related to post creation, editing, and deletion are only accessible to authenticated users.
Adding Markdown Support
Blog posts are often written in Markdown, a lightweight markup language that is easy to read and write. Let‘s add support for Markdown to our blog so we can write posts using this format.
We‘ll use the Earmark library to parse Markdown. First, add it to your dependencies in mix.exs
:
{:earmark, "~> 1.4"}
Then run mix deps.get
to install it.
We can use Earmark to parse the body
of our posts when we display them. Open up the post_view.ex
file and add a helper function to parse Markdown:
# lib/blog_web/views/post_view.ex
def markdown(body) do
body
|> Earmark.as_html!()
|> raw()
end
This function takes the body
of a post, parses it as Markdown using Earmark, and then returns the resulting HTML as a raw string that can be safely included in our template.
Now we can update our show.html.eex
template to use this helper function:
<article class="post">
<div class="post-body">
<%= markdown(@post.body) %>
</div>
<footer>
Published on <%= @post.published_at %>
</footer>
</article>
Now when we view a post, the body
will be parsed as Markdown and displayed as HTML.
Adding Pagination
As our blog grows, we may end up with too many posts to comfortably display on a single page. Let‘s add pagination to split up our posts into multiple pages.
We‘ll use the Scrivener library to paginate our posts. First, add it to your dependencies in mix.exs
:
{:scrivener_ecto, "~> 2.7"}
Then run mix deps.get
to install it.
Now we can update our Blog.list_posts/1
function to paginate the results:
# lib/blog.ex
def list_posts(opts \\ []) do
query = from p in Post, order_by: [desc: p.published_at]
query = if Keyword.get(opts, :published, false), do: where(query, [p], not is_nil(p.published_at)), else: query
Repo.paginate(query, Keyword.merge([page_size: 10], opts))
end
This will return a %Scrivener.Page{}
struct containing the paginated results and information about the current page, total pages, and more.
Update the index
action in the PostController
to pass the pagination options from the query string:
# lib/blog_web/controllers/post_controller.ex
def index(conn, params) do
page = Blog.list_posts(published: true, page: params["page"])
render(conn, "index.html", posts: page.entries, page: page)
end
Lastly, update the index.html.eex
template to display the pagination links:
<%= for post <- @posts do %>
<article class="post">
<h2><%= link post.title, to: Routes.post_path(@conn, :show, post) %></h2>
<p><%= post.body |> String.slice(0, 200) %>...</p>
</article>
<% end %>
<%= if @page.page_number > 1 do %>
<%= link "← Prev", to: Routes.post_path(@conn, :index, page: @page.page_number - 1) %>
<% end %>
<%= for n <- (@[email protected]_pages), n != @page.page_number do %>
<%= link n, to: Routes.post_path(@conn, :index, page: n) %>
<% end %>
<%= if @page.page_number < @page.total_pages do %>
<%= link "Next →", to: Routes.post_path(@conn, :index, page: @page.page_number + 1) %>
<% end %>
Now when we view the post index, it will display the posts paginated with links to go forward and backward through the pages.
Conclusion
And there we have it – a fully-featured blog application built with Elixir and Phoenix! We‘ve covered all the basics of generating a Phoenix project, creating database schemas with migrations, building CRUD functionality in controllers, and authenticating users. We also added some more advanced features like Markdown parsing and pagination.
There‘s still plenty of ways we could enhance and expand our blog. A few ideas:
- Add commenting functionality
- Support multiple user roles (admin, editor, author)
- Generate a sitemap and RSS feed
- Implement full-text searching
- Add tagging and categorization of posts
I hope this guide has given you a solid introduction to building web applications with Elixir and Phoenix. The productivity and performance these tools provide make them a joy to work with. I encourage you to continue exploring the Elixir and Phoenix ecosystem and using them to build your own applications. Happy coding!