Building a blog with Elixir and Phoenix
What's the setup process? How do you create posts? How is the content displayed?
programmingWhat’s the setup process?
Considering you already have Elixir and Phoenix installed, the next step is to create a new project. Since you don’t need a database, you can skip Ecto.
cd /path/to/project
mix phx.new blog --no-ecto
cd blog
Add NimblePublisher to your mix.exs
.
# mix.exs
defp deps do
[
# other stuff...
{:nimble_publisher, "~> 1.0"}
]
end
Run mix setup
in the project directory.
mix setup
Create a module to handle the blog content. I’ll call it Blog
.
defmodule SantosCodes.Blog do
# soon...
end
Create another module to define the struct
for your blog posts. Here, you’ll set all the attributes your blog post requires, besides its content.
defmodule SantosCodes.Blog.Post do
@enforce_keys [:id, :title, :body, :description, :published?]
defstruct [:id, :title, :body, :description, :date, :published?]
def build(filename, attrs, body) do
filename = filename |> String.downcase() |> String.replace(' ', '-')
struct!(__MODULE__, [id: filename, body: body] ++ Map.to_list(attrs))
end
end
Finally, connect your struct with NimblePublisher
. Map where to find the blog post struct and the markdown files you write. More on this in the next section.
defmodule SantosCodes.Blog do
alias SantosCodes.Blog.Post
defmodule NotFoundError do
defexception [:message, plug_status: 404]
end
use NimblePublisher,
build: Post,
from: Application.app_dir(:santos_codes, "priv/posts/*.md"),
as: :posts
@posts @posts |> Enum.sort_by(& &1.date, {:desc, Date}) |> Enum.filter(& &1.published? == true)
def all_posts, do: @posts
def get_post_by_id!(id) do
Enum.find(all_posts(), &(&1.id == id)) ||
raise NotFoundError, "post id=#{id} not found"
end
end
-
The variable
@posts
is where all the parsed blog posts are stored. It’s defined usingas:
. -
Do not access
@posts
multiple times; instead, callall_posts()
whenever you need to use them again. -
I only want the
published?
posts in my list, but this is optional. -
A
NotFoundError
withplug_status: 404
is translated to the HTTP status404 NOT FOUND
for requests trying to access it.
How do you create posts?
Create a new markdown file in the priv/posts/
folder, add a map with the attributes you defined in your post struct, and NimblePublisher
will handle the rest. Adjust as needed if you changed any configuration when using NimblePublisher in the Blog
module. Below is how the markdown file for this post looks.
%{
title: "Building a blog with Elixir and Phoenix",
description: "What's the setup process? How do you create posts? How is the content displayed?",
published?: true
}
---
#### What's the setup process?
Considering you already have Elixir and Phoenix installed, the next step is to create a new project. Since you don't need a database, you can skip Ecto...
How is the content displayed?
Create a new controller — I’ll call it SantosCodesWeb.BlogController
— and add two functions: one for listing the posts, and another for displaying a single post.
defmodule SantosCodesWeb.BlogController do
use SantosCodesWeb, :controller
def index(conn, _params) do
render(conn, :index, posts: SantosCodes.Blog.all_posts())
end
def show(conn, %{"id" => post_id} = _params) do
render(conn, :show, posts: SantosCodes.Blog.get_post_by_id!(post_id))
end
end
Next, update the router:
defmodule SantosCodesWeb.Router do
use SantosCodesWeb, :router
# pipeline setup...
scope "/", SantosCodesWeb do
pipe_through :browser
get "/blog", BlogController, :index
get "/blog/:id", BlogController, :show
end
end
Finally, add templates for your two new routes. Create them in lib/santoscodes_web/controllers/blog_html/
. Remember, santoscodes_web
is my project—yours may have a different name.
# index.html.heex
<h2>My Blog Posts</h2>
<article :for={post <- @posts}>
<h3><%= post.title %></h3>
<p><%= post.description %></p>
</article>
# show.html.heex
<h2><%= @post.title %></h2>
<section>
<%= raw(@post.body) %>
</section>
You can use NimblePublisher to build all kinds of apps that involve publishing content — and it also supports a variety of use cases. For example, I used it to manage request collections in Polo. Have fun!