
Short answer: In Slim 4, add a GET route that queries the newest published rows, builds RSS 2.0 XML with escaped text, absolute links and RFC 822 dates, writes it to the PSR-7 response body and sets the content type to application/rss+xml with a short cache lifetime. Add an ETag so repeat visitors get a 304, test the route with curl and a validator, and then give the public address to an auto-poster so every new item is shared on social media automatically.
Slim is a favourite for small APIs, internal tools and compact websites in PHP. It gives you routing, middleware and a clean PSR-7 request and response, and very little else. That minimalism is exactly why it has no feed support: there is no blog engine to provide one. The solution is a single route. This guide builds it with PDO for the data, explains the parts that go wrong in practice, such as sub-directory installs, escaping and caching, and finishes with the steps that turn the feed into automatic social media posts.
A Slim application often serves content that people want to follow, even though nobody calls it a blog:
A feed removes the need to copy every announcement to social networks by hand. It also lets feed readers, aggregators and auto-posters follow your content in a format that every one of them understands. If you want to understand the structure first, writing an RSS feed by hand explains each element in plain language.
Start from a standard Slim 4 skeleton. If you are creating a new project, install the framework and a PSR-7 implementation with Composer: composer require slim/slim slim/psr7. The example below assumes a MySQL or SQLite table named posts with the columns id, title, slug, summary, status dan published_at. Store the date in UTC. If your application stores local time, convert it before building the feed, because the feed must carry an unambiguous moment.
Put the route in your front controller or in a routes file. The code below uses a PDO connection that the application already has:
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
$siteUrl = 'https://www.example.com';
$app->get('/feed.xml', function (Request $request, Response $response) use ($pdo, $siteUrl) {
$statement = $pdo->prepare(
"SELECT id, title, slug, summary, published_at FROM posts "
. "WHERE status = 'published' AND published_at <= :now "
. "ORDER BY published_at DESC LIMIT 20"
);
$statement->execute(['now' => gmdate('Y-m-d H:i:s')]);
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
$e = fn (string $text): string => htmlspecialchars($text, ENT_XML1 | ENT_QUOTES, 'UTF-8');
$items = '';
foreach ($rows as $row) {
$link = $siteUrl . '/blog/' . $row['slug'];
$date = gmdate(DATE_RSS, strtotime($row['published_at'] . ' UTC'));
$items .= '<item>'
. '<title>' . $e($row['title']) . '</title>'
. '<link>' . $e($link) . '</link>'
. '<guid isPermaLink="false">post-' . (int) $row['id'] . '</guid>'
. '<pubDate>' . $date . '</pubDate>'
. '<description>' . $e((string) $row['summary']) . '</description>'
. '</item>';
}
$xml = '<?xml version="1.0" encoding="UTF-8"?>'
. '<rss version="2.0"><channel>'
. '<title>Example Blog</title>'
. '<link>' . $siteUrl . '/blog</link>'
. '<description>News and articles from Example</description>'
. $items
. '</channel></rss>';
$etag = '"' . md5($xml) . '"';
if ($request->getHeaderLine('If-None-Match') === $etag) {
return $response->withStatus(304)->withHeader('ETag', $etag);
}
$response->getBody()->write($xml);
return $response
->withHeader('Content-Type', 'application/rss+xml; charset=utf-8')
->withHeader('Cache-Control', 'public, max-age=300')
->withHeader('ETag', $etag);
});The route is short, but each line has a reason. The query filters on both the status and the date, so drafts and scheduled posts stay hidden. The escaping helper uses the XML flag, which converts the five special characters and keeps the result valid. The date uses the RSS constant of PHP with gmdate, which writes the time in UTC. The identifier in the GUID comes from the database and is cast to an integer, so it can never contain markup.
The ETag is a fingerprint of the feed. When a client repeats the request with the fingerprint it received last time, the route can answer with a short 304 response and no body. Feed readers and auto-posters send these conditional requests routinely, so a quiet feed costs almost nothing to serve. The mechanism is explained in ETags and conditional GET. If your server also compresses responses, make sure the ETag still changes when the content does.
The most common Slim surprise is the base path. If the application lives in a sub-directory, for example https://www.example.com/app/, the route /feed.xml will answer a 404 until you tell Slim where it lives. Call $app->setBasePath('/app') before you define routes, or configure the web server so that the application sits at the root. Also check that the rewrite rule sends requests for feed.xml to index.php. Some servers have a rule that treats any path with a file extension as a static file, so the request never reaches Slim. A quick test with curl shows which case you are in: a 404 from the web server looks different from a 404 produced by Slim.
| RSS element | Source in the app | Rule |
|---|---|---|
| title | post title | Escape it; keep it short and descriptive |
| link | site address plus slug | Always absolute and public |
| guid | database identifier | Never changes, even if the slug does |
| pubDate | publication date | UTC, RFC 822 format, never in the future |
| description | summary | Plain text or escaped HTML; the first sentence matters most |
Two of these rules cause most of the support questions. Use the identifier, not the slug, for the GUID; otherwise renaming a post makes it look new, and an auto-poster may share it twice, as described in why your RSS feed auto-posts the same article twice. And keep dates in the format explained in RSS date formats and pubDate errors. If summaries contain HTML, escape them or use CDATA; see CDATA and HTML in RSS descriptions.
A closure is fine for a first version, but a real project is easier to maintain when the feed has its own class. Create a small class named FeedController with an __invoke method, receive the database connection and the site address through the constructor, and register the route with the class name. If you use a container such as PHP-DI, it builds the controller and supplies the dependencies. The query and the XML building can then live in separate methods, which makes them easy to test: a unit test can pass a list of fake rows to the builder and compare the output with an expected string. Tests catch the mistakes that hurt feeds most, namely unescaped characters, wrong date formats and items that should have been hidden.
Once the route works, you can add a second route for a category, such as /feed/news.xml atau /feed/{category}.xml, and pass the category name to the same query as an extra condition. Separate feeds let you send different kinds of content to different channels, for example announcements to a company page and tutorials to a developer community. Validate the category against a list of known values before using it, and answer with a 404 for anything else. The routing idea behind this is explained in category filtering for auto-posting.
Slim lets you add middleware around routes, and two kinds are useful for a feed. Error middleware should catch database failures and return a plain 503 status instead of a stack trace or, worse, an empty feed. An empty file can look like a deletion to some tools, while an error status simply makes them try again later. A caching layer in front of the application, such as a reverse proxy or a content delivery network, uses the cache header to keep one copy for a few minutes. That is plenty, and it protects the database from bursts of requests. Avoid very long lifetimes; they delay every share, as explained in RSS feed caching explained. The right content type is covered in the Content-Type guide.
curl -i https://www.example.com/feed.xml and check for a 200 status, the XML content type, the cache header and the ETag. The method is described in debugging an RSS feed with curl.If-None-Match set to the ETag and confirm that you receive a 304.PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed.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 item, with its 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 Slim feed is one route. Query only published items, escape every value, use a stable identifier for the GUID, write dates in UTC in the RSS format, build absolute links from a configured address and return the XML with the correct content type, a short cache and an ETag. Remember the base path for sub-directory installs, keep warnings out of the output and return an error status instead of an empty file. Then test with curl and a validator, and connect the address to an auto-poster so each new item is shared without manual work.
No. Slim is a micro-framework for routing and middleware, so you build the XML yourself in a route, as shown above, or use a small feed library of your choice.
Slim does not know the application lives in a sub-directory. Set the base path before defining routes and make sure the web server rewrites requests to the front controller.
Output before the XML declaration, such as a PHP warning, a debug line or a blank line in an included file, breaks the document. Disable error display in production and send messages to a log file.
Ten to twenty of the newest published items are enough. Auto-posters only need recent items, and a smaller file downloads and parses faster.
Yes. Add the public address of the feed as a source in PostRSS, connect your social accounts and each new item is posted automatically. Check that the address opens without a login.
{“@context”: “https://schema.org”, “@type”: “FAQPage”, “mainEntity”: [{“@type”: “Question”, “name”: “Does Slim have a built-in RSS feature?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “No. Slim is a micro-framework for routing and middleware, so you build the XML yourself in a route, as shown above, or use a small feed library of your choice.”}}, {“@type”: “Question”, “name”: “Why does my feed route return 404 in a sub-directory?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Slim does not know the application lives in a sub-directory. Set the base path before defining routes and make sure the web server rewrites requests to the front controller.”}}, {“@type”: “Question”, “name”: “Why is my feed invalid after I added logging?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Output before the XML declaration, such as a PHP warning, a debug line or a blank line in an included file, breaks the document. Disable error display in production and send messages to a log file.”}}, {“@type”: “Question”, “name”: “How many items should the feed contain?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Ten to twenty of the newest published items are enough. Auto-posters only need recent items, and a smaller file downloads and parses faster.”}}, {“@type”: “Question”, “name”: “Can I use the Slim feed with PostRSS?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Yes. Add the public address of the feed as a source in PostRSS, connect your social accounts and each new item is posted automatically. Check that the address opens without a login.”}}]}
What changed in the networks, what broke, and how to fix it before it costs you reach.
Dibuat oleh Internet Solutions, tim di balik PostRSS. Masing-masing menghemat waktu Anda dengan caranya sendiri.