Building a blog with Elixir and Phoenix

What's the setup process? How do you create posts? How is the content displayed?

programming

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.

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 using as:.
  • Do not access @posts multiple times; instead, call all_posts() whenever you need to use them again.
  • I only want the published? posts in my list, but this is optional.
  • A NotFoundError with plug_status: 404 is translated to the HTTP status 404 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!

References