Screencasts
Home Tag Author Help

Build an RSS reader with Alpine.js

Alpine.js is a tiny JavaScript framework that makes declarative rendering super easy, without the weight of larger frameworks like Vue or React.

While I was playing around with it, I took some time to build a simple, personal RSS reader for fun. Here's what I came up with!

It's pretty ugly, but it works great.

New to Alpine.js? Check out the Learn Alpine.js course over on Codecourse.

Let's get started.

Install Alpine.js

Alpine is designed to be used with a CDN script, rather than any complex Webpack setup, so all we need is a single HTML file with the script pulled in in the head.

Shoot over to the Alpine GitHub page for the latest version CDN script.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Alpine.js RSS</title>

        <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
    </head>
    <body>
        
    </body>
</html>

Initialise the component

We're using just one component here, with an external setup function to initialise the scope. Our scope contains a feed to hold each item in our (soon to be) list of resolved feed items, a sources array containing the hardcoded list of RSS feeds we want to pull from, and an init function to kick the process off.

The feeds I've chosen to test this with are the latest Codecourse courses feed and the Alpine.js Newsletter feed. Feel free to add your own!

<div x-data="setup()" x-init="init">
            
</div>

<script>
    function setup() {
        return {
            feed: [],

            sources: [
                'https://codecourse.com/api/rss/courses',
                'https://buttondown.email/alpinejs/rss'
            ],

            init () {
                console.log('Good to go!')
            }
        }
    }
</script>

Run this in the browser and you should just see Good to go! output in the console.

Parse the RSS

Parsing RSS is nothing short of a nightmare, particularly with different RSS standards to deal with. Let's lean on the rss-parser package to do this for us.

We'll pull this in from jsDelivr to save some time. Pop the script just underneath Alpine in the head.

<script src="https://cdn.jsdelivr.net/npm/rss-parser@3.7.6/dist/rss-parser.min.js"></script>

Now we'll iterate over each of the feeds and read them. Update your setup function with a get function and updated init function so it looks like this. Don't forget to instantiate the RSSParser at the top.

let parser = new RSSParser()

function setup() {
    return {
        feed: [],

        sources: [
            'https://codecourse.com/api/rss/courses',
            'https://buttondown.email/alpinejs/rss'
        ],

        get (source) {            
            parser.parseURL(source, (err, feed) => {
                if (err) { }

                feed.items.forEach(entry => {
                    console.log(entry)
                    
                })
            })
        },

        init () {
            this.sources.forEach(source => {
                this.get(source)
            })
        }
    }
}

For each feed we iterate on, we use the RSS parser to fetch and parse the document. Then we iterate over each entry and log it. We're silently failing if a feed can't be loaded because ain't nobody got time for that.

Check your console and you should see a list of entries as objects, with properties like title and url.

Time to add them to our feed.

Adding parsed entries to our feed

First, create an addToFeed function. This takes the parsed entry and returns an object with just the data we need. You can always add additional properties if you fancy.

addToFeed (item) {
    this.feed.push({
        author: item.author,
        title: item.title,
        link: item.link,
        date: new Date(item.pubDate),
    })
},

Notice we're also wrapping the date in a JavaScript Date object. That'll make it easier to format the date and sort our feed items in order of when they were published.

Now update the get function to invoke addToFeed for each feed item.

get (source) {
    parser.parseURL(source, (err, feed) => {
        if (err) { }

        feed.items.forEach(entry => {
            this.addToFeed(entry)
        })
    })
},

Output the feed!

This one's easy. Just update the component to iterate over each feed item.

<div x-data="setup()" x-init="init">
    <template x-for="entry in feed">
        <div>
            <span x-text="entry.title"></span> <a x-bind:href="entry.link" x-text="entry.link"></a>
            <span x-text="entry.date.toDateString()"></span>
        </div>
    </template>
</div>

As we iterate over each feed item (which we're calling entry) we output the title, link and formatted date.

If you're new to Alpine.js, x-for iterates over an array and renders the content inside the template for each item. x-text sets the innerText of the element you use it on and x-bind:href sets the href attribute of the anchor we're using to the given value.

In your browser, you should now see a list of feed items!

Sorting by published date

Right now the items are grouped by publication and not sorted according to the date they were published.

Let's create a pseudo computed property (a function... Alpine doesn't have computed properties) to sort the items. This is made easier, because we're working with a JavaScript Date object.

Add a sortedFeed function to your setup function.

sortedFeed () {
    return this.feed.sort((first, second) => {
        return second.date.getTime() - first.date.getTime()
    })
},

This is just standard JavaScript sorting, in descending order.

Now update the template to invoke this function instead of directly referencing the feed array.

<div x-data="setup()" x-init="init">
    <template x-for="entry in sortedFeed()">
        <div>
            <span x-text="entry.title"></span> </span><a x-bind:href="entry.link" x-text="entry.link"></a>
            <span x-text="entry.date.toDateString()"></span>
        </div>
    </template>
</div>

We're done!

There you go. A simple, chronologically ordered RSS reader with your chosen publications, built in a single HTML file!

Full code

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Alpine.js RSS</title>

        <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
        <script src="https://cdn.jsdelivr.net/npm/rss-parser@3.7.6/dist/rss-parser.min.js"></script>
    </head>
    <body>
        <div x-data="setup()" x-init="init">
            <template x-for="entry in sortedFeed()">
                <div>
                    <span x-text="entry.title"></span> </span><a x-bind:href="entry.link" x-text="entry.link"></a>
                    <span x-text="entry.date.toDateString()"></span>
                </div>
            </template>
        </div>

        <script>
            let parser = new RSSParser()

            function setup() {
                return {
                    feed: [],

                    sources: [
                        'https://codecourse.com/api/rss/courses',
                        'https://buttondown.email/alpinejs/rss'
                    ],

                    sortedFeed () {
                        return this.feed.sort((first, second) => {
                            return second.date.getTime() - first.date.getTime()
                        })
                    },

                    get (source) {
                        parser.parseURL(source, (err, feed) => {
                            if (err) { }

                            feed.items.forEach(entry => {
                                this.addToFeed(entry)
                            })
                        })
                    },

                    addToFeed (item) {
                        this.feed.push({
                            author: item.author,
                            title: item.title,
                            link: item.link,
                            date: new Date(item.pubDate),
                        })
                    },

                    init () {
                        this.sources.forEach(source => {
                            this.get(source)
                        })
                    }
                }
            }
        </script>
    </body>
</html>