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!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *