A lightweight, scalable RSS feed service built with Hono.js and Upstash Redis. This service allows you to create and manage RSS feeds programmatically through a simple REST API. It's designed to work seamlessly with the @curatedotfun/rss
plugin in the curate.fun ecosystem.
Deploy your own RSS service with one click:
- Multiple Feed Formats: Generate RSS 2.0, Atom, and JSON Feed formats
- Standard-Compliant URLs: Access feeds via standard paths (
/rss.xml
,/atom.xml
,/feed.json
) - Raw Data Option: Get content without HTML via
/raw.json
for frontend customization - HTML Sanitization: Secure content handling with sanitize-html
- Simple Authentication: API secret-based authentication for feed management
- Configurable CORS: Cross-origin request support
- Redis Storage: Efficient storage with Upstash Redis (production) or Redis mock (development)
- Docker Support: Easy local development with Docker and Docker Compose
- Rate Limiting: Protects public endpoints from abuse (100 requests per 5 minutes per IP)
- Security Headers: Essential security headers for better protection
- Request Timeout: 30-second timeout protection for all requests
The service includes rate limiting for public endpoints (GET requests):
- 100 requests per 5 minutes per IP address
- Rate limit headers included in responses:
X-RateLimit-Limit
: Maximum requests per windowX-RateLimit-Remaining
: Remaining requests in current windowX-RateLimit-Reset
: Time when the rate limit resets.
Write operations (POST, PUT) are protected by API key authentication and not rate-limited.
All responses include essential security headers specifically chosen for an RSS service:
-
X-Content-Type-Options: nosniff
- Critical for an RSS service that serves multiple content types (RSS XML, Atom XML, JSON)
- Ensures browsers strictly respect our content type headers
- Prevents content type confusion that could lead to security issues
- Example: Ensures RSS XML is always treated as XML, not executed as something else
-
Strict-Transport-Security: max-age=31536000; includeSubDomains
- Forces browsers to use HTTPS for all requests to your domain
- Essential for protecting feed content and API credentials in transit
- max-age=31536000: Remember this rule for 1 year
- includeSubDomains: Apply to all subdomains too
All requests have a 30-second timeout protection to prevent resource exhaustion.
Endpoint | Method | Description | Authentication | Response Format |
---|---|---|---|---|
/ |
GET | Health check and redirect to preferred format | No | Redirect |
/rss.xml |
GET | Get feed as RSS 2.0 XML | No | application/rss+xml |
/atom.xml |
GET | Get feed as Atom XML | No | application/atom+xml |
/feed.json |
GET | Get feed as JSON Feed (with HTML content) | No | application/json |
/raw.json |
GET | Get feed as JSON Feed (without HTML content) | No | application/json |
/api/items |
GET | Get all items as JSON | No | application/json |
/api/items?format=html |
GET | Get all items with HTML preserved | No | application/json |
/api/items |
POST | Add an item to the feed | Yes | application/json |
The RSS service uses a simple API secret for authentication. Protected endpoints (like POST operations) require the API secret in the Authorization header:
Authorization: Bearer <your-api-secret>
Public endpoints (health check and feed retrieval) do not require authentication, making it easy for RSS readers to access your feed.
The API secret is configured through the API_SECRET
environment variable and should be kept secure. This should match the secret used by services posting to the feed.
Variable | Description | Required | Default |
---|---|---|---|
API_SECRET |
Secret key for API authentication | Yes | - |
UPSTASH_REDIS_REST_URL |
Upstash Redis REST URL | Yes (for production, serverless) | - |
UPSTASH_REDIS_REST_TOKEN |
Upstash Redis REST token | Yes (for production, serverless) | - |
ALLOWED_ORIGINS |
Comma-separated list of allowed origins for CORS | No | * |
PORT |
Port to run the server on | No | 4001 |
USE_REDIS_MOCK |
Set to 'true' to use Redis mock for local development | No | false |
NODE_ENV |
Environment mode (development/production) | No | development |
The RSS service can be configured through the /api/config
API endpoint:
Endpoint | Method | Description | Authentication | Response Format |
---|---|---|---|---|
/api/config |
GET | Get current feed configuration | No | application/json |
/api/config |
PUT | Update feed configuration | Yes | application/json |
Example configuration request:
curl -X PUT https://your-service.com/api/config \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-secret" \
-d '{
"title": "My RSS Feed",
"description": "A feed of curated content",
"siteUrl": "https://example.com",
"language": "en",
"copyright": "© 2025",
"author": {
"name": "Feed Author",
"email": "[email protected]"
},
"maxItems": 100,
"image": "https://example.com/logo.png"
}'
For local development, you can use Docker or run directly:
docker compose up
The service will be available at http://localhost:4001
- Create a
.env
file (cp .env.example .env
):
API_SECRET=your-secure-random-string
USE_REDIS_MOCK=true
PORT=4001
- Start the development server:
npm install
npm run dev
Click the "Deploy on Railway" button at the top of this README to:
- Create a new project from our template
- Set up all necessary infrastructure
- Configure the deployment settings
After deployment, you only need to:
- Set the
API_SECRET
environment variable in your Railway dashboard
- Click the "Deploy to Netlify" button above
- Add required environment variables:
UPSTASH_REDIS_REST_URL
UPSTASH_REDIS_REST_TOKEN
API_SECRET
For deployment to Cloudflare Workers:
-
Install Wrangler:
npm install -g wrangler
-
Add secrets:
wrangler secret put UPSTASH_REDIS_REST_URL
wrangler secret put UPSTASH_REDIS_REST_TOKEN
wrangler secret put API_SECRET
- Deploy:
wrangler publish
Access your feed at:
/rss.xml
(RSS 2.0)/atom.xml
(Atom)/feed.json
(JSON Feed)/raw.json
(Raw JSON)
Add items using the API:
curl -X POST http://localhost:4001/api/items \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-secret" \
-d '{"title":"Test Item","content":"<p>Test content</p>","link":"https://example.com/test"}'
MIT