How to create RSS feed in Phoenix

At we implemented RSS feed with Phoenix. I wanted to share our experience and what it takes to build something like that and how easy it is to add it. This article also shows some tips on working with time and some gotchas that you need to be aware of while building an RSS feed.

Simplest thing that works

To start off, we need to have a controller and a route in place. In our router, we added following lines:

  scope "/rss", ElixirStream do
    get "/", FeedController, :index

I chose to move it to /rss scope since other, web related routes, use custom pipe_trough functions that allow to do stuff like user authentication and add browser request specific plugs to the middleware layer.

Next off we need to add a controller for the RSS feed. It looks like this:

defmodule ElixirStream.FeedController do
  use ElixirStream.Web, :controller
  alias ElixirStream.Entry

  def index(conn, _params) do
    entries = Repo.all from e in Entry, order_by: [desc:], preload: [:user]
     |> put_layout(:none)
     |> put_resp_content_type("application/xml")
     |> render "index.xml", items: entries

We fetch all entries and apply put_layout(:none) function to a connection, to avoid adding layout to our response. After that, we add a content type of "application/xml" and finally render index.xml with entries set as assigns.

The view layer looks the following way:

defmodule ElixirStream.FeedView do
  use ElixirStream.Web, :view
  use Timex

  def parse_markdown(markdown), do: Earmark.to_html(markdown)

  def date_format(entry) do
    {:ok, date } = entry.inserted_at
    |> Ecto.DateTime.to_iso8601
    |> DateFormat.parse("{ISOz}")
    {:ok, date} = DateFormat.format(date, "%a, %d %b %Y %H:%M:%S %z", :strftime)


Our view layer contains only two functions, date_format/1 for formatting time in RFC 822 format of RSS spec and parse_markdown/1 for rendering markdown content to HTML. For markdown we used library Earmark and for working with time there is this awesome library Timex.

The template layer was the trickiest one since almost all feeds in the wild implement the XML little bit differently. So the end result looked like the following:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <description>Community driven place for all things Elixir</description>
    <link><%= entry_url(@conn, :index) %></link>
    <%= for entry <- @items do %>
          <![CDATA[<%= entry.title %>]]>
        <link><%= entry_url(@conn, :show, entry.slug) %></link>
          <![CDATA[<%= parse_markdown(entry.body) %>]]>
        <pubDate><%= date_format(entry)  %></pubDate>
        <guid isPermaLink="true"><%= entry_url(@conn, :show, entry.slug) %></guid>
    <% end %>

Nothing fancy here, except for the gotcha, that I needed to wrap HTML content in CDATA to ensure that it shows up properly.

Final notes

If you are testing your RSS feed with Feedly and nothing shows up, it is kind of impossible to tell what has gone wrong, so the suggestion is to check with other readers first and then, try to view your URL with Feedly. It takes some time for them to show up your content when you fixed all the issues.

Hope you enjoyed!

Here is the link to the feed: