mirror of
https://github.com/rmdes/indiekit-endpoint-rss.git
synced 2026-09-22 21:17:36 +00:00
RSS feed reader endpoint for Indiekit. Aggregates multiple feeds, caches in MongoDB, displays on frontend.
- JavaScript 87.7%
- Nunjucks 8.9%
- CSS 3.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The publish form hardcoded "{{description}}" as the content field's
fallback, whatever the post type. That placeholder runs the item through
stripHtml, so an article feed saved straight from the form published its
items as one flat block of text: links, lists and code spans all gone,
and markdown then applied smart quotes to what used to be code.
jf2-builder already knows the right default per post type -
defaultContent() returns {{content}} for an article - and
applyFeedSettings already falls back to it when content is empty, using
the post type actually submitted. The form was overriding that with a
value the user never chose.
The field is now empty by default and shows the post type's default as
a placeholder, so the server-side default applies for every type.
Existing feeds keep whatever they have stored; set the field to
{{content}} to switch one over.
|
||
| assets | ||
| lib | ||
| locales | ||
| test | ||
| views | ||
| .gitignore | ||
| CLAUDE.md | ||
| index.js | ||
| package.json | ||
| README.md | ||
| renovate.json | ||
@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
minItemsPerFeed: 10, // Newest items always kept, whatever their age
maxPostsPerCycle: 10 // Max posts created per sync cycle
})
],
// 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:
- Go to
/rssapi - Enter feed URL in the "Add Feed" form
- 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=jsonor?f=greader)
FreshRSS Integration
This plugin has special support for FreshRSS (and other Google Reader API compatible aggregators):
- Get your FreshRSS API feed URL (Settings → Authentication → API feed URL)
- Add
?f=jsonor?f=greaderto the URL - 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
originobject) - 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 numberlimit(number, default: 20, max: 100) - Items per pagefeedId(string, optional) - Filter by feed IDincludeContent(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