
Short answer: A MongoDB feed is a query, a template and a route. Store each article with a slug, a status and a publication date saved as a real date, query the newest published documents whose date has passed, project only the fields the feed needs, build the XML with escaped text and send it with the RSS content type, an ETag and a short cache. Add a compound index that matches the query, keep a last good copy for outages and refresh the cache with a change stream or a short timer. Test the live address, then connect it to an auto-poster.
MongoDB stores documents that look like the objects in your application, which makes it a natural source for a blog, a news section or a product update log. Nothing in the database knows about feeds, and that is an advantage, because you decide exactly what appears in the output. It is also a risk, because loose data produces loose feeds: dates saved as strings in two formats, documents without slugs, drafts that look like published articles. This guide shows a clean document shape, the index, the query and a short route written for Node and Express, and explains how to cache the result and how to test it. Driver details change with versions, so check the documentation of the one you use.
Each article needs a few fields with strict types. The slug is a string that never changes and forms the address. The status says whether the article is public. The publication date must be stored as a date type and not as text, because only dates sort and compare correctly across years and time zones. The title and the summary are plain text without markup.
{
slug: "mongodb-feed-example",
title: "A short example article",
summary: "One or two complete sentences that describe the article.",
status: "published",
publishedAt: ISODate("2026-11-04T09:00:00Z"),
body: "..."
}Do not use the identifier of a document, which contains the time of its creation, as the publication date. An article may be written on Monday and published on Friday, and a feed that follows the identifier would order it wrongly. Do not use a last-modified field either, because a correction would move the article to the top and could make an auto-poster share it again. The causes of repeated posts are explained in why a feed auto-posts the same article twice.
| Feed element | Source in the document | Rule |
|---|---|---|
| Title | title | Plain text, no markup |
| Link and identifier | slug plus your site address | Absolute, https, never changes |
| Publication date | publishedAt | A date type, set once |
| Description | summary | One or two complete sentences |
The feed query filters on the status, compares the date and sorts by it. A compound index with the same order lets the database read the twenty newest entries without scanning the collection.
db.articles.createIndex({ status: 1, publishedAt: -1 })
db.articles.createIndex({ slug: 1 }, { unique: true })The unique index on the slug protects you from two documents with the same address, which would give two items the same identifier. Run the explain command on the query once and check that it uses the index. A feed that is called every few minutes by several tools should never cause a full scan.
The route below uses the official driver, Express and Node 18 or later. It reads the newest published documents, builds the XML and sends it with the right headers. If the database fails, it serves the last good copy.
import express from "express";
import { MongoClient } from "mongodb";
import { createHash } from "node:crypto";
const SITE = "https://example.com";
const client = new MongoClient(process.env.MONGODB_URI);
const articles = client.db("site").collection("articles");
const ENT = { "&": "&" + "amp;", "<": "&" + "lt;", ">": "&" + "gt;", '"': "&" + "quot;", "'": "&" + "apos;" };
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ENT[c]);
async function load() {
return articles
.find({ status: "published", publishedAt: { $lte: new Date() } })
.project({ _id: 0, slug: 1, title: 1, summary: 1, publishedAt: 1 })
.sort({ publishedAt: -1 })
.limit(20)
.toArray();
}
function render(docs) {
const newest = docs.length ? docs[0].publishedAt : new Date(0);
const items = docs.map((d) => {
const url = SITE + "/blog/" + d.slug + "/";
return `<item>
<title>${esc(d.title)}</title>
<link>${esc(url)}</link>
<guid isPermaLink="true">${esc(url)}</guid>
<pubDate>${d.publishedAt.toUTCString()}</pubDate>
<description>${esc(d.summary)}</description>
</item>`;
}).join("\n");
return `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel>
<title>Example Journal</title>
<link>${SITE}</link>
<description>News and guides from Example Journal.</description>
<lastBuildDate>${newest.toUTCString()}</lastBuildDate>
${items}
</channel></rss>`;
}
let cache = { xml: null, etag: null, at: 0 };
const app = express();
app.get("/feed.xml", async (req, res) => {
try {
if (!cache.xml || Date.now() - cache.at > 300000) {
const xml = render(await load());
cache = { xml, etag: '"' + createHash("sha1").update(xml).digest("hex") + '"', at: Date.now() };
}
} catch (err) {
if (!cache.xml) return res.status(503).send("Feed temporarily unavailable");
}
res.set({
"Content-Type": "application/rss+xml; charset=utf-8",
"Cache-Control": "public, max-age=300",
ETag: cache.etag,
});
if (req.get("If-None-Match") === cache.etag) return res.status(304).end();
res.send(cache.xml);
});
await client.connect();
app.listen(3000);The projection leaves out the body of each article, which can be large, and the query returns plain objects with real dates, so the conversion to the GMT form is exact. The cache is a single variable with a time stamp, which is enough for one process. If you run several, each keeps its own copy, and the validators are identical as long as the data is the same.
A cache of five minutes means that a new article appears in the feed within five minutes. For faster updates, use a change stream, which tells your application when documents change. It needs a replica set or a hosted cluster, which most managed services provide. Watch for inserts and updates that set the status to published, and clear the cache when one arrives. Keep the timer as well, because a stream can disconnect, and scheduled articles become visible when their date passes, which is not a database event. Background on delays is in why auto-posted content sometimes lags.
The flexibility of documents means that bad data gets in easily. Add a validation rule to the collection that requires a slug, a title, a summary, a status from a fixed list and a date type for the publication date. Reject empty strings. Run a one-time check for documents that already violate the rules, in particular articles with a date stored as text, which the query ignores silently or sorts wrongly. A feed that omits an article without any error is hard to debug, so test with a draft, a scheduled article, an article with a special character and an article with a long title.
A feed route is public, but your database must not be. Give the application a database user that can only read the articles collection, keep the connection string in an environment variable and never pass request parameters into the query. If you offer filters by category or language, take the value from a fixed list and not from the address as it arrives, because a crafted parameter can change the meaning of a query. Restrict network access to the database so that only your servers can connect, and enable the authentication and encryption that your provider offers. The feed itself needs no more than a read permission, so a leak of its credentials should never allow changes.
An auto-poster needs only the newest items, so twenty entries are enough. Do not return the whole collection to be helpful. A large feed is slow, may time out and gives no benefit, because tools read the first entries and compare them with what they already know. If you want to offer an archive to people, build it as a normal page with pagination on the website and keep the feed short. If you publish several articles within a minute, for example after a bulk import, raise the limit for that day so that none is pushed out before a tool reads it. See how many items a feed should contain.
If your collection covers several languages, store the language in each document and create one route per language, so that every language has its own feed and its own social channel. A feed that mixes translations makes an auto-poster share the same story several times, and followers see it as spam. Categories work the same way: add a category field, filter on it in the query and offer one address per topic. See category filtering for auto-posting și the guide to multilingual feeds.
curl -i https://example.com/feed.xml
curl -i -H 'If-None-Match: "paste-the-etag-here"' https://example.com/feed.xmlA longer routine is in the feed validation guide, and a shorter one in debugging a feed with curl.
PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed, connect your accounts and choose how each post is built from the title, the description or the page title. PostRSS checks the feed as often as every minute and shares each new entry, with its image and link, to the channels you choose among 66 supported networks, messengers, team chats and blogs, including LinkedIn, X, Bluesky, Mastodon and Telegram. Keyword filters, UTM parameters and posting windows let you tune each channel. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.
A MongoDB feed depends on disciplined data. Store the slug, the status and a publication date as a real date, add a compound index, query only what the feed needs, escape the text, send the right headers and cache the result with a fallback. Validate documents so that bad data cannot slip in, refresh the cache with a change stream and a timer, test the live address from outside and then connect it to an auto-poster, so that every article you publish reaches your social channels without extra work.
Query the newest published documents, build the XML with escaped text in a route, and send it with the RSS content type, an ETag and a short cache.
A publication date stored as a real date and set once when the article goes live. Do not use the identifier or a last-modified field.
Use a change stream to clear the cache when a document is published, and keep a short timer as a safety net for scheduled articles and dropped connections.
Common causes are a status that does not match, a date stored as text, a date in the future or a missing slug. Check the document against the validation rules.
Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new articles are shared as soon as they appear.
What changed in the networks, what broke, and how to fix it before it costs you reach.
Create de Internet Solutions, echipa din spatele PostRSS. Fiecare produs vă economisește timp în felul său.