RSS ke 66 jejaring sosial: Facebook, Instagram, X, LinkedIn, Telegram, dan lainnya Blog Afiliasi Kontak
Masuk Mulai gratis
Updated: 2026-10-20
Elysia RSS Feed on Bun: ETag Route and Auto-Post

Short answer: To serve an RSS feed from an Elysia app running on Bun, add a GET route for /feed.xml, read the newest published items from bun:sqlite or your own database, build the RSS 2.0 XML with a small escape helper, format dates with toUTCString and return a Response with the content type application/rss+xml. Add an ETag taken from a hash of the XML and a short cache header so repeat requests get a cheap 304 answer, test the address with curl and a validator, and then give the public URL to an auto-poster so every new item reaches your social channels.

Bun is a fast JavaScript and TypeScript runtime with a built-in test runner, bundler and SQLite driver, and Elysia is a web framework designed around it. Together they suit small services, personal projects and APIs that start quickly and need few dependencies. Sooner or later such a service publishes something that people want to follow: articles, release notes, listings or event dates. A feed is the standard way to let readers, aggregators and auto-posters watch that content without an account or an API key. This guide builds a complete Elysia RSS feed with caching and conditional requests, explains each decision and ends with the checks that keep the feed dependable once other systems rely on it.

When an Elysia service needs a feed

A feed is worth adding whenever the service publishes things that people outside the application want to follow:

  • articles, news or announcements stored in a database and shown through a separate front end;
  • release notes or a changelog for a public API or package;
  • listings, events or offers added by staff or by users;
  • any content that should also appear on social networks without being copied by hand.

If your content already lives in a blog or a CMS, use the feed it provides instead; see how to find your WordPress feed link. For the same job in other JavaScript frameworks, see the Hono guide.

Set up the project

Create a project with Bun and add Elysia. The example reads from a SQLite file with the driver that is built into Bun, so there is nothing else to install. Replace it with the client for your own database when you move to production.

mkdir elysia-feed && cd elysia-feed
bun init -y
bun add elysia

Keep the public address of the site in an environment variable rather than reading it from the incoming request. Behind a proxy, the request may show an internal host name, and a feed full of internal addresses is useless to every reader. Bun loads a file named .env automatically, so you can set SITE_URL, SITE_TITLE and DB_PATH there.

The server

The route below does five things in order: it reads the newest published rows, builds the XML, hashes it into an ETag, answers a matching If-None-Match header with a 304 and otherwise returns the feed. The condition on the publication time matters. Without it, an item scheduled for tomorrow would appear in the feed today, and an auto-poster would share it before it exists on the website.

import { Elysia } from 'elysia'
import { Database } from 'bun:sqlite'

const SITE_URL = process.env.SITE_URL ?? 'https://www.example.com'
const SITE_TITLE = process.env.SITE_TITLE ?? 'Example News'
const db = new Database(process.env.DB_PATH ?? 'data/site.db', { readonly: true })

type Row = { id: number; title: string; slug: string; summary: string; published_at: string }

const latest = db.query(
  `SELECT id, title, slug, summary, published_at
     FROM posts
    WHERE status = 'published' AND published_at <= ?1
    ORDER BY published_at DESC
    LIMIT 20`
)

const esc = (s: string) => String(s ?? '')
  .replaceAll('&', '&amp;')
  .replaceAll('<', '&lt;')
  .replaceAll('>', '&gt;')
  .replaceAll('"', '&quot;')
  .replaceAll("'", '&apos;')

function buildFeed(rows: Row[]): string {
  const items = rows.map((r) => `
    <item>
      <title>${esc(r.title)}</title>
      <link>${esc(`${SITE_URL}/${r.slug}/`)}</link>
      <guid isPermaLink="false">post-${r.id}</guid>
      <pubDate>${new Date(r.published_at).toUTCString()}</pubDate>
      <description>${esc(r.summary)}</description>
    </item>`).join('')
  return `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>${esc(SITE_TITLE)}</title>
    <link>${esc(SITE_URL)}/</link>
    <description>${esc(`The latest posts from ${SITE_TITLE}`)}</description>
    <language>en</language>
    <atom:link href="${esc(SITE_URL)}/feed.xml" rel="self" type="application/rss+xml"/>${items}
  </channel>
</rss>
`
}

new Elysia()
  .get('/feed.xml', ({ request }) => {
    const rows = latest.all(new Date().toISOString()) as Row[]
    const xml = buildFeed(rows)
    const etag = '"' + new Bun.CryptoHasher('sha256').update(xml).digest('hex').slice(0, 16) + '"'
    const headers = {
      'Content-Type': 'application/rss+xml; charset=utf-8',
      'Cache-Control': 'public, max-age=300',
      ETag: etag,
    }
    if (request.headers.get('if-none-match') === etag) {
      return new Response(null, { status: 304, headers })
    }
    return new Response(xml, { headers })
  })
  .listen(3000)

console.log('Feed on http://localhost:3000/feed.xml')

Start it with bun run index.ts and open the route in a browser or with curl. You should see well-formed XML with at most twenty items, newest first. Returning a Response object lets you control the status and the headers exactly, which is what a feed needs.

Where each field comes from

Feed fieldSource in the exampleRule to keep
titleThe title columnPlain text, escaped, no markup
linkSite address plus slugAbsolute address that returns 200 directly
guidThe row id with a prefixNever changes, even if the slug changes
pubDateThe publication time in UTCEnglish day and month names, never in the future
descriptionThe summary columnOne or two sentences that make sense alone

The identifier is the most important decision. An auto-poster remembers which items it has already shared by their GUID. If the value changes when someone edits a slug or republishes an article, the same story is shared again. Using the database id with a fixed prefix avoids that. The problem and its fixes are described in why a feed auto-posts the same article twice.

Dates in the English format

RSS dates follow RFC 822, with English abbreviations for days and months. The toUTCString method of the Date object prints exactly that shape, such as a three-letter day, the date, a three-letter month, the year, the time and the word GMT. Do not use toLocaleString or Intl formatters for feeds, because they follow the language of the server or the user; a different locale would print month names that readers cannot parse. Store publication times in an unambiguous format such as ISO 8601 with a zero offset, so that the conversion never depends on the time zone of the machine. If you see date warnings in a validator, read the guide to RSS date format errors.

Conditional requests and caching

Auto-posters, readers and search engines request a feed often, and almost always nothing has changed. The route above handles that in two ways. It sends an ETag that is a short hash of the finished XML, and when the client returns the same value in the If-None-Match header, the answer is a 304 with no body. It also sends a Cache-Control header that lets a proxy or a content delivery network keep the response for a few minutes. Together they cut the cost of each check to one query and one hash.

If traffic is high, keep the finished XML in a variable together with the time it was built, and rebuild it only when a post is published or after a short interval. The background to both techniques is in the guide to ETags and conditional GET dan the guide to feed caching. Do not cache for hours, because a long cache delays every new post by the same amount. Many hosting platforms also compress responses for you; see the guide to feed compression before adding your own.

Images and richer items

An auto-poster is more useful when each item carries a picture. Add an enclosure element with the address, the byte length and the type of the image, or a media content element if you prefer that namespace. Use an absolute https address and a file of reasonable size. Without an image, many networks show a plain text card. How tools read these elements is explained in the guide to enclosure tags. If the summary contains HTML, wrap it in a CDATA block or escape it, as described in the CDATA guide.

Test the feed before you connect it

  1. Request the route with curl and look at the headers: the status should be 200, the content type should be application/rss+xml and an ETag should be present.
  2. Repeat the request with the ETag in an If-None-Match header and confirm that a 304 comes back.
  3. Paste the public address into the W3C Feed Validation Service and fix every error.
  4. Insert a test row and check that it appears at the top of the feed.
  5. Set a publication time in the future and check that the item stays hidden until then.

There is a longer checklist in the feed validation guide and a short curl routine in the guide to debugging a feed with curl.

Deploying a Bun service behind a proxy

On a server, run the service under a process manager or a container, and put a reverse proxy in front of it that terminates HTTPS. Pass the original host and scheme through, but do not depend on them for the feed, because the address comes from your environment variable. Check that the proxy does not strip the ETag and Cache-Control headers, and that it does not add its own cache that keeps old XML for too long. After every deployment, request the feed once to confirm that it still returns 200, and make sure the address is exactly the one you gave to the auto-poster. If the feed ever stops updating, this checklist shows what to inspect first.

Common mistakes to avoid

  • Unescaped text. One ampersand in a title can make the whole feed invalid.
  • Future items. Filter by publication time so scheduled content stays hidden until its moment.
  • Unstable GUIDs. Use an identifier that never changes.
  • Localised dates. Use toUTCString, never a locale-aware formatter.
  • Addresses taken from the request. Build links from the configured public address.
  • Changing the feed address. Keep the route, or redirect the old one permanently.

How PostRSS works with an Elysia feed

PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed 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 item, 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, 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.

Bacaan terkait

The bottom line

Elysia on Bun needs only one route, a query and a small template function to serve a dependable RSS feed: select published items dated in the past, escape every value, use a stable GUID, format dates in English UTC, build links from a configured public address and return a Response with the right content type, an ETag and a short cache. Test the route with curl and a validator, keep the address unchanged and connect it to an auto-poster. After that, every item you publish can reach your social channels automatically.

FAQ

Does Elysia have a built-in RSS generator?

No. Elysia has no feed support in its core. You build the XML yourself or use a small library, and the route simply returns a Response with the right content type.

Can I use bun:sqlite in production?

It works well for small and read-heavy services, and the example opens the file read-only. For larger systems use the client of your own database and keep the same query and the same escaping rules.

Why add an ETag to the feed response?

Auto-posters and readers check feeds very often. When the ETag has not changed, they receive a 304 answer without a body, which saves bandwidth and processing on both sides.

How many items should the feed contain?

Between ten and twenty of the newest published items is enough. An auto-poster only needs recent items, and a smaller file is faster to download and parse.

How do I stop old posts from being shared again?

Give every item a stable identifier and use it as the GUID. Avoid editing the publication date of items that are already public, because some tools treat a changed date as a new item.

Can I use this feed with PostRSS?

Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new items are shared as soon as they appear.

New guides, once a month

What changed in the networks, what broke, and how to fix it before it costs you reach.

We send a confirmation e-mail first. Unsubscribe any time.

Alat lain dari tim kami

Dibuat oleh Internet Solutions, tim di balik PostRSS. Masing-masing menghemat waktu Anda dengan caranya sendiri.