RSS do 66 sieci społecznościowych: Facebook, Instagram, X, LinkedIn, Telegram i innych Blog Program partnerski Kontakt
Zaloguj się Zacznij za darmo
Updated: 2026-10-30
Sails.js RSS Feed: A Controller Action, Dates and Caching

Short answer: In a Sails.js application, an RSS feed is one route that points to an action returning XML. Add the route to the routes file, load the newest published records with Waterline, build the document with escaped text and absolute links, set the RSS content type and let the response answer with a 304 status when nothing has changed. Test the live address with curl and a validator. When the address, the links and the dates stay stable, an auto-poster can read the feed and share every new post on your social networks.

Sails is an MVC framework for Node.js that sits on top of Express. It generates routes, models and controllers, and it is often used for APIs and for server-rendered sites with a Waterline database layer. A feed fits it well: the data already lives in a model, the route is one line of configuration and the action is a short function. The risks are the usual ones for a hand-built feed: text that breaks the XML, links that are not absolute, dates in the wrong format and an identifier that changes when somebody edits a post. This guide builds a complete action, explains each choice and ends with a test routine.

Add the route

Routes in Sails are listed in a single configuration file. Add one entry for the feed address and point it to an action. The action name follows the folder structure of the controllers.

// config/routes.js
module.exports.routes = {
  "GET /": { action: "home/index" },
  "GET /feed.xml": { action: "feed/show" },
};

If you use blueprint routes, make sure that the feed address does not collide with an automatic route, and check that no policy in the policies file blocks anonymous requests to the action, because an auto-poster has no login. The same idea in other Node.js frameworks is covered in the Express feed guide oraz the NestJS feed guide.

Model the posts

A feed item needs a title, a permanent address, a publication date and a summary. Add those fields to the model of your posts, and keep the slug separate from the title so that it never changes after the first save.

// api/models/Post.js
module.exports = {
  attributes: {
    slug: { type: "string", required: true, unique: true },
    title: { type: "string", required: true },
    summary: { type: "string", required: true },
    published: { type: "boolean", defaultsTo: false },
    publishedAt: { type: "number", description: "Epoch milliseconds" },
  },
};

The date is stored as a number of milliseconds, which sorts correctly in every database that Waterline supports and avoids time zone surprises. The feed converts it to the format that the RSS specification expects. If you store dates in another type, convert them in one place only.

Write the action

The action loads the newest published posts, builds the XML and sends it. Keep it in its own file so that it can be tested without the rest of the application.

// api/controllers/feed/show.js
const SITE = "https://example.com";

const ENT = {
  "&": "&" + "amp;",
  "<": "&" + "lt;",
  ">": "&" + "gt;",
  '"': "&" + "quot;",
  "'": "&" + "apos;",
};
const esc = (s) => String(s || "").replace(/[&<>"']/g, (c) => ENT[c]);

module.exports = async function show(req, res) {
  const posts = await Post.find({
    where: { published: true, publishedAt: { "<=": Date.now() } },
    sort: "publishedAt DESC",
    limit: 20,
  });

  const newest = posts.length ? posts[0].publishedAt : 0;
  const items = posts.map((p) => {
    const url = `${SITE}/blog/${p.slug}`;
    return `<item>
      <title>${esc(p.title)}</title>
      <link>${esc(url)}</link>
      <guid isPermaLink="true">${esc(url)}</guid>
      <pubDate>${new Date(p.publishedAt).toUTCString()}</pubDate>
      <description>${esc(p.summary)}</description>
    </item>`;
  }).join("\n");

  const xml = `<?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>${new Date(newest).toUTCString()}</lastBuildDate>
  ${items}
</channel></rss>`;

  res.set("Content-Type", "application/rss+xml; charset=utf-8");
  res.set("Cache-Control", "public, max-age=300");
  return res.send(xml);
};

Several details are deliberate. The query keeps drafts and future posts out. The channel date comes from the newest post and not from the clock, so that the file does not change when nothing was published. The identifier of each item is its permanent address. And the address is built from a fixed constant, so every link is absolute. Action files in Sails may also be written in the newer style with inputs and exits; the body stays the same, and you can reach the response object from the action context to set the headers.

Conditional requests

The response object of Sails is the one from Express, and Express creates a weak validator for a string body and compares it with the header that the client sends. When the two match, the framework answers with a 304 status and no body. You can see this in practice by requesting the feed twice with curl and sending back the validator from the first answer. If you want the validator to depend on something other than the text, for example the date of the newest post, you can set the header yourself before sending. A 304 answer saves bandwidth for you and for every tool that polls the feed. The mechanism is explained in the guide to ETags and conditional requests, and the status codes in the guide to feed status codes.

Where each field comes from

Feed elementSource in the codeRule
Titletitle attributePlain text, escaped
Link and identifierSITE and slugAbsolute, https, never changes
Publication datepublishedAtIn the past, never the current time
Descriptionsummary attributeOne or two complete sentences
Channel dateNewest publishedAtChanges only when a post is added
Cache headermax-age in the actionShort enough that new posts appear quickly

The identifier deserves the most attention. If the slug follows the title and someone corrects a typo, the address changes, and an auto-poster that stores identifiers will treat the old and the new address as two different posts. Generate the slug when the record is created and never change it afterwards. The causes and cures are covered in why a feed auto-posts the same article twice.

Dates, drafts and imports

The query already keeps drafts and future posts out. That matters, because a draft that leaks into the feed is shared on social networks before it exists on your website. If you import old posts into the database, give them their original dates, because a feed full of items with today’s date makes an auto-poster share your whole archive in one afternoon. If you publish on a schedule, make sure that the publication date is the moment the post becomes visible, not the moment that it was written. For the effect of old dates see the guide to feed order and backdated posts.

Summaries, images and encoding

Auto-posters build the best social posts from a feed that gives them a short summary and a picture. Write a real summary for every post instead of cutting the text automatically, because an automatic cut can end in the middle of a sentence and that is exactly what the auto-poster will copy. If a post has an image, add it as an enclosure with an absolute address, a type and a length. Whether to include the full text or only a summary is discussed in full text or excerpt in an RSS feed. Text arrives from the database as Unicode, and the response declares UTF-8, but control characters that XML forbids can still come from pasted content, so strip them when you save the record. See why special characters and emoji break feeds.

It also helps to keep the feed code in one small helper with its own test. Give the function a post whose title contains an ampersand, a quotation mark and an emoji, and confirm that the output parses as XML. That single test catches the most common production failure, and it protects you later when somebody changes the model and forgets that the feed depends on it.

Make the feed discoverable

Add a link element to the head of your layout view, with the relation set to alternate, the type set to the RSS media type and the absolute address of the feed. A person or a tool that only knows your home page can then find the feed on its own. The full explanation is in the guide to feed autodiscovery.

Test the feed

Lift the application and request the route locally, then test the deployed address from outside, because a local test says nothing about the proxy and the certificate in front of your application.

sails lift
curl -i http://localhost:1337/feed.xml
curl -i https://example.com/feed.xml
curl -i -H 'If-None-Match: "paste-the-etag-here"' https://example.com/feed.xml
  • The first two requests return 200, the RSS content type and a body that starts with an XML declaration.
  • The last request returns 304 and no body when the value matches the one the server sent.
  • Every link is absolute, uses https and points to your real domain.
  • Paste the address into the W3C Feed Validation Service and fix every error.
  • Publish a post, reload the feed and confirm that the new item comes first.

A longer routine is in the feed validation guide, and a shorter one in debugging a feed with curl.

Common mistakes to avoid

  • A policy that blocks the route. The auto-poster has no login, so the feed must be public.
  • Unescaped text. One ampersand in a title breaks the whole document.
  • The clock as the channel date. The file changes on every request and caching stops working.
  • Slugs that follow titles. An edit changes the address and the post may be shared twice.
  • Drafts in the list. An article is shared before it exists on the website.
  • Only a local test. The proxy in front of the application changes headers and caching.

How PostRSS works with a Sails.js feed

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.

Powiązane materiały

The bottom line

A Sails feed is one route and one action, but it has to get the details right: a query that returns only published posts with stable slugs, escaped text, absolute links, a channel date taken from the newest post, the correct content type and a validator that lets clients skip unchanged files. Make sure that no policy blocks the route, test the live address with curl and a validator, then connect it to an auto-poster. After that, every post you publish reaches your readers and your social channels without extra work.

FAQ

How do I return XML from a Sails action?

Set the Content-Type header on the response to the RSS media type and send the XML string as the body. The response object is the same one that Express provides.

Why does my feed route ask for a login?

A policy is probably applied to the action, or to all actions by default. Allow anonymous access for the feed action in the policies file.

Which date type should I use for the posts?

A number of milliseconds sorts reliably and converts to the date format that RSS expects. Whatever type you choose, convert it to a UTC string in one place only.

How do I support conditional requests?

Express generates a validator for a string response and answers with a 304 status when the client sends the same value back. You can also set the validator yourself from the date of the newest post.

Can PostRSS read a feed served by a Sails application?

Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new posts 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.

Więcej narzędzi od naszego zespołu

Tworzy je Internet Solutions, zespół stojący za PostRSS. Każdy produkt oszczędza czas na swój sposób.