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.