
Short answer: A Fastify RSS feed is a single GET route. Load the newest published posts, build RSS 2.0 XML with escaped text and absolute links, and send it with reply.type('application/rss+xml; charset=utf-8').send(xml). Add a short Cache-Control header, compress the response and validate the output once. Then paste the feed address into an auto-poster, and every new post is shared on your social channels without any manual work.
Fastify is a Node.js web framework known for low overhead and a clean plugin system. It does not include a feed generator, and it does not need one: an RSS feed is only a response with a special content type. This guide shows a complete route, a PostgreSQL data source, caching, compression, error handling and the mistakes that most often break feeds. At the end you will see how to share each item automatically.
An RSS document is plain XML served over HTTP. Fastify already handles routing, headers and serialization, so the only decisions are where the posts come from and how to turn them into XML. The data can live in a SQL database, a document store, Markdown files or an upstream API. If your content sits in a system without a native feed, the idea is the same as in auto-posting from a headless CMS without native RSS: build the feed yourself and let the auto-poster read it.
If you have built feeds in other Node.js frameworks, the pattern will be familiar from the Express RSS feed guide. The difference in Fastify is mostly in the reply API and in how plugins are registered.
This example uses the official PostgreSQL plugin and a table named posts with the columns id, slug, title, summary, image, published_at এবং status:
import Fastify from 'fastify';
import postgres from '@fastify/postgres';
const SITE = 'https://www.example.com';
const app = Fastify({ logger: true });
await app.register(postgres, { connectionString: process.env.DATABASE_URL });
const esc = (value) => String(value ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
app.get('/rss.xml', async (request, reply) => {
const { rows } = await app.pg.query(
'SELECT id, slug, title, summary, image, published_at FROM posts '
+ 'WHERE status = $1 ORDER BY published_at DESC LIMIT $2',
['published', 20]
);
const items = rows.map((post) => {
const link = SITE + '/blog/' + post.slug;
const image = post.image ? new URL(post.image, SITE).href : '';
return '<item>'
+ '<title>' + esc(post.title) + '</title>'
+ '<link>' + esc(link) + '</link>'
+ '<guid isPermaLink="false">post-' + post.id + '</guid>'
+ '<pubDate>' + new Date(post.published_at).toUTCString() + '</pubDate>'
+ '<description>' + esc(post.summary) + '</description>'
+ (image ? '<enclosure url="' + esc(image) + '" type="image/jpeg" length="0"/>' : '')
+ '</item>';
});
const xml = '<?xml version="1.0" encoding="UTF-8"?>'
+ '<rss version="2.0"><channel>'
+ '<title>Example Blog</title>'
+ '<link>' + SITE + '/blog</link>'
+ '<description>News and articles from Example</description>'
+ items.join('')
+ '</channel></rss>';
return reply
.type('application/rss+xml; charset=utf-8')
.header('Cache-Control', 'public, max-age=300')
.send(xml);
});
await app.listen({ port: 3000, host: '0.0.0.0' });The handler is asynchronous and returns the reply, which is the style Fastify recommends. The feed is available at /rss.xml as soon as the server starts. Replace the query with your own data access if you do not use PostgreSQL; everything below the query stays the same.
The helper esc converts ampersands and angle brackets into entities. Without it, a title such as “Tips & tricks” produces invalid XML, and many readers and auto-posters stop at the first error. If your summaries contain HTML, escape them or wrap them in CDATA; the rules are described in CDATA and HTML in RSS descriptions.
The query filters on status and sorts by published_at, newest first, with a limit of 20. A feed does not need your whole archive, and a short feed is fetched faster. Always sort by the publication date, not by the update date, otherwise an edited old post jumps to the top and may look new.
The GUID uses the database ID. If you used the slug instead, renaming a post would create a new identifier and an auto-poster could share the article twice; see why your RSS feed auto-posts the same article twice. Dates are written with toUTCString(), which produces the RFC 822 format that RSS expects. Typical date mistakes are covered in RSS date formats and pubDate errors.
Feeds are fetched often, and the XML compresses very well. Two official plugins handle this without changing the route:
Both reduce traffic without any effect on the content. The background is explained in RSS feed compression with gzip and Brotli and in RSS feed ETags and conditional GET.
The data in a feed changes rarely, but the feed is requested constantly by readers, crawlers and auto-posters. You have three layers to choose from:
Keep the lifetime short. A feed cached for a whole day delays every share by a day, which defeats the purpose of automation. Five minutes is a sensible balance for most blogs.
If the database is unavailable, an unhandled error returns a JSON error object with status 500. A feed reader or auto-poster that receives it will treat the feed as broken and may retry aggressively. A safer pattern is to catch the failure, log it and return a 503 status with a Retry-After header, so clients know the problem is temporary:
try {
// build and send the feed
} catch (err) {
request.log.error(err);
return reply.code(503).header('Retry-After', '120').type('text/plain').send('Feed temporarily unavailable');
}The meaning of each status code for auto-posting is described in RSS feed HTTP status codes explained. Never return an empty but valid feed as a fallback: an empty channel looks like a healthy feed with no news, and nothing will warn you.
Items are read outside your website, so every address must be complete. A relative link such as /blog/my-post or an image path such as /img/cover.jpg works in a browser on your site but fails in a reader or on a social network. The call new URL(path, SITE) turns a relative image path into an absolute one and keeps addresses that are already absolute. This class of mistake is described in relative URLs in RSS feeds.
Choose images about 1200 pixels wide and set the correct type for each file instead of hard-coding JPEG when your covers can also be PNG or WebP. Sizes for each network are listed in social media image sizes.
Fastify routes accept parameters, so one handler can serve a feed for every category. Register app.get('/category/:name/rss.xml', ...), read the value from request.params.name and add a category = $3 condition to the query. Always pass the value as a parameter, never as part of the SQL text. Move the XML builder into a function that takes a title and a list of posts, and reuse it for the main feed and for each category.
Separate feeds let you send news to one channel, tutorials to another and announcements to a team chat, instead of publishing everything everywhere. The idea is explained in category filtering for auto-posting.
Production Node.js applications usually sit behind a reverse proxy, a load balancer or a platform router. Two details matter for feeds. First, build every link from the fixed SITE constant rather than from the incoming host header, otherwise the feed may contain an internal address or an http link when your public address is https. Second, if you do read the client address or protocol, start Fastify with the trustProxy option so that it trusts the headers your proxy adds. Finally, make sure the proxy does not strip or rewrite the content type and does not apply its own long cache to the route. A quick check from outside your network, after every deployment, catches most of these problems before an auto-poster does.
reply.type() with application/rss+xml; see the Content-Type guide.curl -i and check that the status is 200 and the content type is application/rss+xml. The method is described in debugging an RSS feed with curl.The framework and its plugins are documented at fastify.dev.
PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your rss.xml route, 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 post, 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, hashtags from categories and posting windows let you tune each channel, and the REST API and webhooks connect PostRSS to your own systems. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.
A Fastify RSS feed is a single GET route that returns XML. Load the newest published posts, escape every text value, use a stable ID for the GUID, make links and images absolute and send the content type application/rss+xml with a short cache lifetime. Add compression and ETags with official plugins, answer failures with a 503 status and validate the result once. After that, every post you publish can reach your social channels automatically.
No. Fastify is a general web framework. You add the feed as a normal route that builds RSS XML from your posts and returns it with the correct content type.
Any address works. The names /rss.xml and /feed.xml are common. You can also add a link tag with the type application/rss+xml to your page head so that readers and tools can discover the feed.
For a simple blog, a short function that builds the XML is easy to read and has no extra dependency. If you need many options, a feed library can help, but you still need to set the content type and the cache headers yourself.
Many browsers offer to download files of the type application/rss+xml. That is normal. Use curl or a validator to inspect the content, and test the address in your auto-poster.
Yes. Paste the public feed address as a source in PostRSS, connect your social accounts and each new item is posted automatically. Make sure the address opens without a login first.
{“@context”: “https://schema.org”, “@type”: “FAQPage”, “mainEntity”: [{“@type”: “Question”, “name”: “Does Fastify have a built-in RSS feed generator?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “No. Fastify is a general web framework. You add the feed as a normal route that builds RSS XML from your posts and returns it with the correct content type.”}}, {“@type”: “Question”, “name”: “Which address should the feed have?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Any address works. The names /rss.xml and /feed.xml are common. You can also add a link tag with the type application/rss+xml to your page head so that readers and tools can discover the feed.”}}, {“@type”: “Question”, “name”: “Should I use a plugin or write the XML by hand?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “For a simple blog, a short function that builds the XML is easy to read and has no extra dependency. If you need many options, a feed library can help, but you still need to set the content type and the cache headers yourself.”}}, {“@type”: “Question”, “name”: “Why does my feed download instead of opening in the browser?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Many browsers offer to download files of the type application/rss+xml. That is normal. Use curl or a validator to inspect the content, and test the address in your auto-poster.”}}, {“@type”: “Question”, “name”: “Can I use the Fastify feed with PostRSS?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Yes. Paste the public feed address as a source in PostRSS, connect your social accounts and each new item is posted automatically. Make sure the address opens without a login first.”}}]}
What changed in the networks, what broke, and how to fix it before it costs you reach.
PostRSS-এর পেছনের টিম Internet Solutions-এর তৈরি। প্রতিটি পণ্য নিজের মতো করে আপনার সময় বাঁচায়।