RSS feed reader endpoint for Indiekit. Aggregates multiple feeds, caches in MongoDB, displays on frontend.
  • JavaScript 77.7%
  • Nunjucks 14.7%
  • CSS 7.6%
Find a file
Ricardo 3620af9bd9 fix: handle lastSync as ISO string in status endpoint
syncState.lastSync is stored as an ISO string but status controller
was calling .getTime() and .toISOString() on it as if it were a Date
object, causing a 500 error on /rssapi/api/status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:44:21 +01:00
assets fix: use upstream @indiekit/frontend form classes 2026-02-12 20:14:44 +01:00
lib fix: handle lastSync as ISO string in status endpoint 2026-02-18 11:44:21 +01:00
locales fix: add missing widget.title locale key across all languages 2026-02-17 10:19:10 +01:00
views fix: use upstream @indiekit/frontend form classes 2026-02-12 20:14:44 +01:00
CLAUDE.md docs: add CLAUDE.md and README.md 2026-02-13 18:22:55 +01:00
index.js feat: auto-prune RSS items older than 30 days 2026-02-03 13:38:57 +01:00
package.json fix: handle lastSync as ISO string in status endpoint 2026-02-18 11:44:21 +01:00
README.md docs: add CLAUDE.md and README.md 2026-02-13 18:22:55 +01:00

@rmdes/indiekit-endpoint-rss

RSS feed reader endpoint for Indiekit. Aggregates multiple feeds, caches in MongoDB, displays on frontend.

Features

  • Multi-Feed Aggregation - Subscribe to unlimited RSS/Atom/JSON feeds
  • Background Sync - Automatic updates every 15 minutes (configurable)
  • MongoDB Caching - Fast access to cached feed items
  • Admin Dashboard - Add/remove feeds, view recent items, trigger manual sync
  • Public JSON API - Read-only endpoints for frontend display
  • Feed Management - Enable/disable feeds, view sync status and errors
  • Automatic Cleanup - Prunes items older than 30 days (configurable)
  • Format Support - RSS 2.0, Atom, JSON Feed, Google Reader API (FreshRSS)
  • Concurrency Control - Fetches 3 feeds in parallel to avoid overwhelming servers
  • HTML Sanitization - Strips dangerous tags, keeps basic formatting

Installation

npm install @rmdes/indiekit-endpoint-rss

Configuration

Add to your indiekit.config.js:

import RssEndpoint from "@rmdes/indiekit-endpoint-rss";

export default {
  plugins: [
    new RssEndpoint({
      mountPath: "/rssapi",          // Default: /rssapi
      syncInterval: 900_000,         // 15 minutes (in milliseconds)
      maxItemsPerFeed: 50,           // Max items per feed to cache
      fetchTimeout: 10_000,          // 10 second timeout per feed
      maxConcurrentFetches: 3,       // Parallel feed fetches
      retentionDays: 30              // Days to keep items
    })
  ],
  // MongoDB database is REQUIRED
  mongodbUrl: process.env.MONGODB_URL
};

Usage

Admin Dashboard

Navigate to /rssapi (or your configured mountPath) to access the admin dashboard.

Features:

  • View all subscribed feeds
  • Add new feeds by URL
  • Enable/disable feeds
  • Remove feeds
  • View recent items
  • Manual sync trigger
  • Clear cache and re-sync

Adding Feeds

Via Admin UI:

  1. Go to /rssapi
  2. Enter feed URL in the "Add Feed" form
  3. Click "Add Feed"

Via API:

curl -X POST https://yoursite.com/rssapi/api/feeds \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"url": "https://example.com/feed.xml"}'

Public API Endpoints

List All Feeds:

GET /rssapi/api/feeds

List Feed Items (Paginated):

GET /rssapi/api/items?page=1&limit=20&feedId=FEED_ID&includeContent=true

Get Single Item:

GET /rssapi/api/items/:id

Sync Status:

GET /rssapi/api/status

Example: Display Items in Eleventy

// _data/rssItems.js
export default async function () {
  const response = await fetch("https://yoursite.com/rssapi/api/items?limit=10");
  const data = await response.json();
  return data.items;
}
<!-- index.njk -->
<h2>Latest News</h2>
{% for item in rssItems %}
  <article>
    <h3><a href="{{ item.link }}">{{ item.title }}</a></h3>
    <p class="meta">{{ item.feedTitle }} - {{ item.pubDate | date("PPp") }}</p>
    <p>{{ item.description }}</p>
    {% if item.imageUrl %}
      <img src="{{ item.imageUrl }}" alt="">
    {% endif %}
  </article>
{% endfor %}

Supported Feed Formats

  • RSS 2.0 - Standard blog feeds
  • Atom - GitHub releases, podcasts, etc.
  • JSON Feed - jsonfeed.org spec (v1.0, v1.1)
  • Google Reader API - FreshRSS, NewsBlur, Miniflux (use ?f=json or ?f=greader)

FreshRSS Integration

This plugin has special support for FreshRSS (and other Google Reader API compatible aggregators):

  1. Get your FreshRSS API feed URL (Settings → Authentication → API feed URL)
  2. Add ?f=json or ?f=greader to the URL
  3. Add the URL to Indiekit RSS endpoint

Example:

https://freshrss.example.com/api/greader.php/reader/api/0/stream/contents/reading-list?f=json&n=50&user=admin&apiKey=YOUR_API_KEY

Features:

  • Preserves original feed source (via origin object)
  • Filters out internal FreshRSS categories
  • Extracts images from media content
  • Handles FreshRSS timestamps (Unix seconds, microseconds)

API Reference

GET /api/feeds

List all feeds.

Response:

{
  "feeds": [
    {
      "id": "507f1f77bcf86cd799439011",
      "url": "https://example.com/feed.xml",
      "title": "Example Blog",
      "siteUrl": "https://example.com",
      "description": "A blog about things",
      "imageUrl": "https://example.com/icon.png",
      "enabled": true,
      "addedAt": "2025-02-01T12:00:00.000Z",
      "lastFetchedAt": "2025-02-13T14:30:00.000Z",
      "lastError": null,
      "itemCount": 42
    }
  ],
  "total": 1
}

GET /api/items

List feed items with pagination.

Query Parameters:

  • page (number, default: 1) - Page number
  • limit (number, default: 20, max: 100) - Items per page
  • feedId (string, optional) - Filter by feed ID
  • includeContent (boolean, default: false) - Include full HTML content

Response:

{
  "items": [
    {
      "id": "507f1f77bcf86cd799439012",
      "feedId": "507f1f77bcf86cd799439011",
      "feedTitle": "Example Blog",
      "guid": "https://example.com/post-1",
      "title": "Hello World",
      "link": "https://example.com/post-1",
      "description": "A short summary...",
      "author": "John Doe",
      "pubDate": "2025-02-13T12:00:00.000Z",
      "imageUrl": "https://example.com/image.jpg",
      "categories": ["tech", "web"],
      "fetchedAt": "2025-02-13T14:30:00.000Z",
      "sourceTitle": "Original Feed Title",
      "sourceUrl": "https://original-source.com"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "totalPages": 5,
    "hasNext": true,
    "hasPrev": false
  }
}

GET /api/items/:id

Get a single item by ID.

Response:

{
  "item": {
    "id": "507f1f77bcf86cd799439012",
    "feedId": "507f1f77bcf86cd799439011",
    "feedTitle": "Example Blog",
    "title": "Hello World",
    "link": "https://example.com/post-1",
    "description": "A short summary...",
    "content": "<p>Full sanitized HTML content...</p>",
    "author": "John Doe",
    "pubDate": "2025-02-13T12:00:00.000Z",
    "imageUrl": "https://example.com/image.jpg",
    "categories": ["tech", "web"],
    "enclosure": {
      "url": "https://example.com/podcast.mp3",
      "type": "audio/mpeg",
      "length": 12345678
    },
    "fetchedAt": "2025-02-13T14:30:00.000Z"
  }
}

GET /api/status

Get sync status and statistics.

Response:

{
  "status": "idle",
  "lastSync": "2025-02-13T14:30:00.000Z",
  "nextSync": "2025-02-13T14:45:00.000Z",
  "lastError": null,
  "stats": {
    "feedsCount": 5,
    "enabledFeedsCount": 4,
    "itemsCount": 200,
    "lastFeedsProcessed": 4,
    "lastItemsAdded": 12
  },
  "config": {
    "syncInterval": 900000,
    "maxItemsPerFeed": 50
  }
}

Requirements

  • Indiekit >= 1.0.0-beta.25
  • MongoDB database
  • Node.js >= 20

License

MIT

Author

Ricardo Mendes - https://rmendes.net

Repository

https://github.com/rmdes/indiekit-endpoint-rss