Computer Things
by Erik Hanson

Creating an RSS feed in Elixir and Phoenix

Hand-rolling an RSS feed with Elixir and Phoenix is pretty straightforward. You just need a list of feed items, a controller, a template, and a view. In this article, I’ll assume the list of items is a list of blog articles.

Articles

A simple way to store your articles is in a list somewhere. A more complicated way is to store it in a database. I’ll assume we’re storing it in a list.

defmodule Core.Articles do
  @articles [
    %{title: "Apples", description: "Red or green fruit", published_at: ~U[2024-02-25 00:00:00Z], slug: "apples"},
    %{title: "Bananas", description: "Yellow fruit", published_at: ~U[2024-02-26 00:00:00Z], slug: "bananas"},
    %{title: "Cherries", description: "Red fruit", published_at: ~U[2024-02-27 00:00:00Z], slug: "cherries"}
  ]

  def articles, do: @articles
end

Controller

All the controller needs to do is render an XML template:

defmodule Web.RssController do
  use Web, :controller

  def index(conn, _params) do
    render(conn, "index.xml", articles: Core.Articles.all(), host: conn.host, port: conn.port)
  end
end

Template

The template needs to include some header information, and then iterate over the articles:

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Favorite Fruit</title>
    <description>A list of fruit</description>
    <link>https://example.com/</link>
    <lastBuildDate><%= pub_date(@articles) %></lastBuildDate>
    <pubDate><%= pub_date(@articles) %></pubDate>
    <ttl>1800</ttl>
    <atom:link href="https://example.com/rss" rel="self" type="application/rss+xml"/>

    <%= for article <- @articles do %>
      <item>
        <title><%= article.title %></title>
        <description><![CDATA[ <%= article.description %> ]]></description>
        <link><%= "https://example.com/#{article.slug}" %></link>
        <guid isPermaLink="false"><%= article.slug %></guid>
        <pubDate><%= pub_date(article) %></pubDate>
      </item>
    <% end %>
  </channel>
</rss>

View

The view needs to implement a couple helper functions for the template.

defmodule Web.RssView do
  use Web, :view

  def pub_date(nil), do: ""
  def pub_date(articles) when is_list(articles), do: List.first(articles) |> pub_date()
  def pub_date(article), do: format_rfc822(article.published_at)

  def format_rfc822(date_time), do: Calendar.strftime(date_time, "%a, %d %b %Y %H:%M:%S %Z")
end

Wrap up

Add a route to your router:

get "/rss", Web.RssController, :index

and add a link to the “head” section of your “app.html.heex” template:

<link rel="alternate" type="application/rss+xml" title="RSS feed for example.com" href="/rss" >

and you’re all done. Easy to do without having to add an external dependency.