Stringer: a Hotwire 8 reader still shipping an old view framework
A self-hosted, anti-social RSS reader.
At a glance
- What is it?
- A self-hosted feed reader whose pitch is what it does not do, with no social recommendations and no machine learning, and whose own repository is a study in two eras of frontend. It ships a Fever-compatible API, four lint linters, and a scheduled task that deletes your read history.
- Who is it for?
- Take Stringer if you want a feed reader you host yourself with no account graph and no recommendation engine, and if a scheduled job that prunes old read items is a feature rather than a loss. Check three things before you deploy it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- stringer-rss/stringer4,125 ★ · -8
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| stringer-rss/stringer | 4.1k | 4.1k |
-8 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The pitch is a list of things it deliberately does not do
The description is unusually blunt for a feed reader, and it is worth quoting the shape of it rather than the words. Stringer has no external dependencies, no social recommendations or sharing, and no machine learning algorithms. Then it says it does have keyboard shortcuts and was made with love. So the product decision is subtraction: a reader that does not know who you are, does not tell you what to read next, and does not call anyone else's server while doing it. That is a coherent position for a self-hosted tool, and it has a practical consequence. Without recommendations, the interface has to make your own filtering good, which is why the feature list leans on search, folders, and stars rather than on discovery. And without accounts beyond your own, there is no sharing to design for, which removes the entire category of features that makes most readers into social products.
Two eras of frontend in one dependency list
The JavaScript manifest is the most informative file in the repository because it does not hide its history. The modern half is the current toolchain: a pinned recent Node version, a pinned recent package manager, the Hotwire trio at version eight, the Rails websocket and upload packages at version eight, and a dev stack built on current major versions of the linter, the test runner, the type checker, and the bundler. The other half is four libraries pinned to versions that have not moved in years: a client-side view library at its final release, a shim that binds it to native views, its own utility library at its final release, a keyboard shortcut library at its final release, and a font icon set stuck several majors behind its current one. So this is a Hotwire application that still ships a client-side view framework and its utility library to the browser. That combination is not an oversight so much as a decision about what a working reader is worth: the view layer is small enough to leave alone, while the surrounding tooling moves. The keyboard shortcut library is pinned because the `?` shortcut that opens the shortcut list is a core promise of the project, and installing from source is one script:
bin/setupA Fever API clone means your mobile client keeps working
One feature distinguishes this reader from most of its self-hosted peers, and it is about not locking you in. Stringer implements a clone of the Fever API, which is a small protocol that mobile clients for reading feeds understand, so a phone app that supports Fever can talk to your own server instead of to a hosted service. The configuration is three values: a server path with the API appended to your instance's address, an email field whose value is a fixed word and which the page notes is case sensitive, and your password. The case-sensitivity note is the kind of detail that saves an afternoon. The strategic point is the same as the project's overall position. If the reader is deliberately feature-poor, the cost of switching becomes the thing to minimise, and a compatibility layer for an existing client ecosystem is cheaper than building a mobile app.
Read history expires after thirty days unless starred
There is one maintenance task documented on the page, and it is worth reading twice because it deletes data. A rake task cleans up old read stories, and by default it removes read stories more than thirty days old that are not starred. It can be run by hand or added as a scheduled task. So there are three things to notice. The window is fixed at thirty days by default, which is a product decision rather than a configuration surface, since the page does not describe a setting for it. Starred items are exempt, which makes starring the mechanism for keeping something. And the task is what stops a long-running reader from accumulating a table of everything you ever read, which is the actual reason an anti-social reader can stay small. On a hosted platform where a database fills up for free, this is the difference between a reader that runs for years and one that eventually cannot, so it is not a footnote.
One Ruby suite and one JavaScript suite, and five linters
The development section is the most detailed part of the page and it describes a project that takes its own quality gates seriously. Setup is one script that installs dependencies, prepares the database, and starts the server on a port; afterwards a second script boots the application server alongside asset watchers, and a rake task gives you a console. There is a toolchain versions file pinning four things at once, which is how a project with this many moving parts keeps a new contributor's machine matching. Tests are two suites: the Ruby one through a spec runner, runnable down to a single example by line number, and the JavaScript one through a different runner, which additionally type-checks and lints before running. Around those sit four separate linters, one for Ruby, one for JavaScript, one for stylesheets, and one for types, plus a security scanner that runs as its own command. The stylesheet linter is configured with a class-naming pattern plugin, which tells you the project enforces a naming convention in its own markup rather than leaving it to review.
Tracked todo files for every linter, and a cron binary fetched by checksum
Three files at the repository root have a name pattern worth naming: there is a todo file for the JavaScript linter, one for the Ruby linter, and one for the stylesheet linter. Those are the conventional places a project records the violations it has chosen not to fix yet, one line each with a suppression comment. Having all three means every linter in the pipeline has an acknowledged backlog rather than a clean sheet, which is the honest way to adopt a strict rule set on an existing codebase. The container definition is similarly explicit in a different way. It installs a small cron runner, downloading it for the target architecture and verifying the download against a per-architecture checksum before installing it, which is how the scheduled cleanup task runs inside a container that has no init system. That same scheduled task is why the binary is there at all: without a cron inside the container, the cleanup would only ever run when somebody remembered to run it.
Docker upgrades have a documented step because the database changed layout
The compose file is three services and one of them exists only to prepare the environment. A setup container runs a Ruby script that initialises or updates the environment file and is configured not to restart, so it runs once and exits. The database service waits for that to finish successfully before starting, which is the ordering guarantee that makes the whole thing reproducible. The application service then waits for the database and publishes port eighty onto the container's port. And the database service carries a comment that is worth more than most release notes: newer database images expect a single mount and store the data in a subdirectory named for the version, so the compose file points at a host directory and refers you to the Docker documentation for the upgrade steps. That is an admission that an existing self-hosted deployment does not upgrade by pulling a newer image. The consequence for you is concrete: read that note before your next upgrade, because a wrong mount there does not fail loudly, it loses the volume.
Editorial conclusion
Take Stringer if you want a feed reader you host yourself with no account graph and no recommendation engine, and if a scheduled job that prunes old read items is a feature rather than a loss. Check three things before you deploy it. There are no GitHub releases, so there is no tag to pin and the deploy button is the front door rather than a versioned artefact. The read-history cleanup is a real task with a thirty-day window that leaves starred items alone, so decide whether you want that before the database fills up. And if you self-host with Docker, read the upgrade note in the compose file first, because the database image changed how it stores data and the migration is not automatic.
Frequently asked questions
What is Stringer used for?
It is a self-hosted feed reader that deliberately omits social recommendations, sharing, external dependencies, and machine learning. What it keeps is keyboard shortcuts, translations, a Fever-compatible API for mobile clients, and a rake task that removes old read stories.
How does Stringer work with mobile apps?
It implements a clone of the Fever API, so any client that supports Fever can connect to your own instance. You configure a server path with the API appended to your address, an email field set to a fixed case-sensitive word, and your password.
How do I run Stringer locally for development?
Install the toolchain versions pinned in the tool-versions file for Ruby, Node, PostgreSQL, and the package manager, then run the setup script, which installs dependencies, prepares the database, and starts the server on port 3000. Later runs use the dev script, which boots the application server with asset watchers.
What does Stringer delete automatically?
A rake task removes read stories more than thirty days old that are not starred. It can be run manually or added as a scheduled task, and the container image installs a cron runner with a checksum-verified download so the schedule works inside the container.
How do I run Stringer in Docker?
The compose file runs three services: a one-shot setup container that prepares the environment file, a Postgres service that waits for it, and the application, which publishes port 80 to the container port. Note that newer database images changed where they store data, so the file points you to the Docker documentation for upgrade steps on an existing deployment.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/stringer-rss-stringer)Star history chart for your README
[](https://hysenlabs.com/projects/stringer-rss-stringer)Was this useful?