
Short answer: An Express RSS feed in Node.js is one route. Install the feed package, load your latest published posts, add each one with an absolute link, a stable ID and a real Date, then send feed.rss2() with the application/rss+xml content type and a short cache. Validate it once and any RSS auto-poster can share every new article automatically.
Express is still the most common way to run a Node.js website or API, and plenty of blogs, documentation sites, product changelogs and news sections are served by a hand-built Express app. Because Express is minimal by design, nothing gives you a feed automatically. That matters more than it seems: a feed is how readers subscribe, how newsletters and aggregators find your latest content, and how social automation tools publish each new post the moment it goes live. This guide shows a reliable way to build the feed, the details that most often break Node feeds, and how to connect the result to social auto-posting.
Start from the requirements. A dependable feed:
Images and categories are optional, but they shape how social posts look, so we add them once the basic feed validates.
Two npm packages cover almost every Express project:
item() call per post and xml() to output the document.Both escape titles and summaries and format dates correctly, which removes the main reasons hand-written template feeds fail. The examples below use feed:
npm install feedThe example assumes a data function getPublishedPosts(limit) that returns posts with title, slug, summary e publishedAt, newest first, and only those whose publication time has passed. The public base URL comes from an environment variable, which is the most predictable way to get correct absolute links.
const express = require('express');
const { Feed } = require('feed');
const app = express();
const BASE_URL = (process.env.BASE_URL || 'https://example.com').replace(//$/, '');
app.get('/rss.xml', async (req, res, next) => {
try {
const posts = await getPublishedPosts(30);
const feed = new Feed({
title: 'Example Blog',
description: 'New articles from Example',
id: BASE_URL + '/',
link: BASE_URL + '/',
language: 'en',
copyright: 'Example',
updated: posts[0] ? new Date(posts[0].publishedAt) : new Date(),
feedLinks: { rss: BASE_URL + '/rss.xml' }
});
for (const p of posts) {
const url = `${BASE_URL}/blog/${p.slug}`;
feed.addItem({
title: p.title,
id: url,
link: url,
description: p.summary,
date: new Date(p.publishedAt)
});
}
res.set('Cache-Control', 'public, max-age=300');
res.type('application/rss+xml; charset=utf-8').send(feed.rss2());
} catch (err) {
next(err);
}
});A few lines deserve attention. The item id is the permanent article URL, which becomes the item’s guid and should never change after publication. The channel updated date follows the newest post instead of the current time, so the feed does not look changed on every request. Errors are passed to next(), so a failed database query produces a proper error response rather than a half-written XML document that tools may misread.
Three details cause most Node feed problems.
Date, not a formatted string. The library outputs the RFC 822 style date RSS expects. If your database returns timestamps without a time zone, make sure they are interpreted as UTC, otherwise every item shifts by the server’s offset.summary contains HTML, decide whether to strip it to plain text or keep it. Plain text produces cleaner social posts; full HTML belongs in the item’s content field, which the library places in content:encoded.Our article on content:encoded versus description explains which field usually becomes the social caption, and the guide to RSS date format errors covers date problems in detail.
The example uses a configured BASE_URL. If you build links from the request instead, with req.protocol e req.get('host'), be careful behind Nginx, a load balancer or a platform router: Express then sees the internal connection, typically plain HTTP.
app.set('trust proxy', 1) tells Express to read X-Forwarded-Proto from one proxy hop, so req.protocol becomes https. Only trust as many hops as you actually have. The Express guide to running behind proxies lists the accepted values.http://, localhost, :3000 and internal host names.Relative links are the other half of the problem: they work in your templates but break once an item is read elsewhere, as explained in our guide to relative URLs and broken links in RSS feeds.
Richer items produce better social posts. After the basic feed validates, add:
image field of addItem accepts an absolute URL, which the library writes as an enclosure. Use a mid-sized picture; many auto-posting tools take it as the post image.category field takes an array of objects with a name. Tools can turn categories into hashtags or filter items by topic.og:title, og:description e og:image; tools often fall back to them.<link rel="alternate" type="application/rss+xml" title="Example Blog" href="https://example.com/rss.xml"> so browsers and tools can find the feed.Because one Feed object can be serialised in several formats, extra formats are nearly free. Move the building code into a function such as buildFeed(posts, title), then add /atom.xml returning feed.atom1() with application/atom+xml, and /feed.json returning feed.json1() if you want JSON Feed. Most auto-posting tools read RSS or Atom.
The same function serves category feeds. Add a route such as /category/:slug/rss.xml, look up the category, return 404 when it does not exist, and pass the filtered posts with a category-specific title. Keep each article’s ID identical in every feed. If duplicates still appear in social channels, the guide on duplicate GUID fixes explains how auto-posters decide what is new.
Feeds are polled constantly by readers, aggregators and auto-posting services, often every few minutes. Without caching, every request queries the database and rebuilds the XML.
res.send() by default, and answers 304 Not Modified when the client’s If-None-Match matches. Well-behaved clients then download the full feed only when it changed.Long cache lifetimes delay auto-posting, so keep them short. Our explanation of why cached feeds make auto-posts lag shows how the delays add up.
A short test with supertest protects the feed. Request /rss.xml, expect status 200 and a content type containing application/rss+xml, then parse the body with an XML parser and check that the first item has a title, an absolute link and a pubDate. Run it in CI so a refactor that breaks the route is caught before release.
| Problem | Typical cause in Express | Fix |
|---|---|---|
| Links use http or port 3000 | trust proxy not set | Configured BASE_URL or app.set(‘trust proxy’) |
| Feed shown as plain text | Default text/html or text/plain type | res.type(‘application/rss+xml; charset=utf-8’) |
| Dates shifted by hours | Timestamps without a time zone | Store and parse timestamps as UTC |
| Half-written XML on errors | Exceptions inside an async route | Catch and pass errors to next() |
| Same post shared twice | Item ID built from an editable title | Use the permanent URL as the ID |
| Feed always looks changed | Channel date set to new Date() | Use the newest post’s date |
Before connecting automation, fetch the headers with curl -I, expect HTTP 200 without redirects, validate the feed and open a few item links in a private window to confirm they are public.
With a valid feed in place, sharing no longer depends on someone remembering to post. Add the feed URL to an auto-posting service, connect your social accounts and channels, decide how posts are built from the title, summary and link, and choose posting hours if you need them. The service checks the feed and publishes each new item once.
JavaScript teams often route content by audience: product news to a professional network, engineering posts to Mastodon or Bluesky, changelog entries to a Discord server, a Telegram channel or a team chat. Category feeds make every route a matter of picking the right feed. More setups are collected under technical guides and the Feed RSS topic.
PostRSS reads any RSS or Atom feed, including one served by an Express route, and publishes each new item to the networks you connect. It checks feeds as often as every minute, takes the image from the feed item or the page’s Open Graph tags, and offers keyword filters, hashtags from item categories and UTM parameters. Developers can also use the PostRSS REST API v1 and outgoing webhooks, and the service has been running since 2014. See the features page for all 66 networks and the pricing page for what each plan includes.
An Express RSS feed in Node.js takes one route and a few careful choices: a feed library for safe XML, a configured HTTPS base URL, real UTC dates, permanent item IDs, the right content type, proper error handling and a short cache. Add images and categories, offer Atom from the same object if you like, cover the route with a supertest check and validate it once. From then on the feed runs quietly, and every new article reaches subscribers and your social channels without manual sharing.
No. Express is a minimal framework, so a feed is a normal route that returns XML. Packages such as feed or rss build the document for you.
Use feed when you want RSS, Atom and JSON Feed from one object. The rss package is a lighter choice when you only need RSS 2.0.
Behind a proxy Express sees the internal HTTP connection. Use a configured base URL, or enable trust proxy so req.protocol reflects the original scheme.
Yes. By default Express adds a weak ETag to bodies sent with res.send and returns 304 Not Modified when the client already has the current version.
Yes. Once the feed is public and valid, an RSS auto-posting service can watch it and publish each new post to the social networks and channels you connect.
What changed in the networks, what broke, and how to fix it before it costs you reach.
Criadas pela Internet Solutions, a equipe por trás do PostRSS. Cada uma economiza seu tempo de um jeito diferente.