README
¶
Gator
A terminal-based RSS feed aggregator written in Go. Gator lets multiple users add RSS feeds, follow feeds shared by others, continuously collect posts into PostgreSQL, and browse the latest articles without leaving the command line.
Table of Contents
- Features
- Tech Stack
- Prerequisites
- Installation
- Database Setup
- Configuration
- Quick Start
- Commands
- Database Schema
- Development
- How It Works
- Troubleshooting
- Acknowledgements
- License
Features
- Multiple local users, each with their own followed feeds
- Add RSS feeds and automatically follow them
- Follow or unfollow feeds added by other users
- Long-running aggregator that fetches the least recently updated feed on a timer
- Posts stored in PostgreSQL and deduplicated by URL
- Browse posts from the feeds you follow, newest first
- Single self-contained binary after installation
Tech Stack
- Go — CLI, HTTP fetching, and RSS/XML parsing
- PostgreSQL — persistent storage
- sqlc — type-safe database code generated from SQL
- Goose — database migrations
- lib/pq — PostgreSQL driver
- google/uuid — UUID primary keys
Prerequisites
To build and run Gator you need:
- Go 1.27.3 or newer
- PostgreSQL 14 or newer running locally or accessible over the network
macOS
brew install go
brew install postgresql@18
brew services start postgresql@18
Debian / Ubuntu / WSL
Install PostgreSQL with apt, and install Go from the official downloads page if your distribution's version is older than 1.27.3.
sudo apt update
sudo apt install postgresql postgresql-contrib
sudo service postgresql start
Verify both tools are available:
go version
psql --version
Gator compiles to a standalone binary. You need Go to install or build it, but you do not need the Go toolchain to run the resulting
gatorbinary.
Installation
Install the CLI with go install:
go install github.com/3mmarx/gator@latest
Go places the binary in $(go env GOPATH)/bin. Make sure that directory is on your PATH:
export PATH="$(go env GOPATH)/bin:$PATH"
Add that line to your ~/.zshrc, ~/.bashrc, or equivalent shell profile to make it permanent.
Check the installation:
gator
Without arguments, Gator prints its usage message.
Build from source
git clone https://github.com/3mmarx/gator.git
cd gator
go build -o gator .
For a fully static binary:
CGO_ENABLED=0 go build -o gator .
Then run the binary directly:
./gator users
Database Setup
1. Create the database
createdb gator
If your PostgreSQL server requires a specific user or password, connect with psql and create the database there instead:
CREATE DATABASE gator;
2. Install Goose
Gator's schema is managed with Goose migrations located in sql/schema:
go install github.com/pressly/goose/v3/cmd/goose@latest
3. Apply the migrations
The migration files live in the repository, so run this from a clone of the project:
git clone https://github.com/3mmarx/gator.git
cd gator
goose -dir sql/schema postgres \
"postgres://postgres:postgres@localhost:5432/gator?sslmode=disable" up
Replace the connection string with your own credentials. On macOS with Homebrew, your local PostgreSQL user is often your system username and has no password:
goose -dir sql/schema postgres \
"postgres://$(whoami)@localhost:5432/gator?sslmode=disable" up
Useful migration commands:
# Show the status of every migration
goose -dir sql/schema postgres "<connection-string>" status
# Roll back the most recent migration
goose -dir sql/schema postgres "<connection-string>" down
Configuration
Gator reads its configuration from a JSON file in your home directory:
~/.gatorconfig.json
Create it before running any command:
{
"db_url": "postgres://postgres:postgres@localhost:5432/gator?sslmode=disable",
"current_user_name": ""
}
| Field | Description |
|---|---|
db_url |
PostgreSQL connection string. The format is postgres://user:password@host:port/database?sslmode=disable. |
current_user_name |
The user Gator acts as. gator register and gator login update this field automatically. |
Examples for common local setups:
{
"db_url": "postgres://ammar@localhost:5432/gator?sslmode=disable",
"current_user_name": "ammar"
}
{
"db_url": "postgres://postgres:postgres@localhost:5432/gator?sslmode=disable",
"current_user_name": "postgres"
}
~/.gatorconfig.jsonstores database credentials. Do not commit it to version control. Gator does not create this file for you.
Quick Start
Register a user. This creates the account and makes it the current user:
gator register ammar
Add a few RSS feeds. Adding a feed also follows it automatically:
gator addfeed "Hacker News" "https://news.ycombinator.com/rss"
gator addfeed "Boot.dev Blog" "https://blog.boot.dev/index.xml"
gator addfeed "TechCrunch" "https://techcrunch.com/feed/"
Confirm what you are following:
gator following
Start collecting posts. This runs until you stop it with Ctrl+C, so leave it in its own terminal:
gator agg 1m
In another terminal, browse the latest posts from the feeds you follow:
gator browse 10
Without a limit, browse shows two posts:
gator browse
Commands
| Command | Description | Requires login |
|---|---|---|
gator register <username> |
Create a new user and set them as the current user | No |
gator login <username> |
Switch to an existing user | No |
gator users |
List all registered users; the current user is marked with * |
No |
gator reset |
Delete all users and, by cascade, their feeds, follows, and posts | No |
gator feeds |
List every feed with its name, URL, and owner | No |
gator addfeed <name> <url> |
Add a feed and automatically follow it | Yes |
gator follow <url> |
Follow a feed that already exists | Yes |
gator following |
List the feeds followed by the current user | Yes |
gator unfollow <url> |
Unfollow a feed | Yes |
gator agg <duration> |
Continuously fetch the least recently fetched feed every <duration> |
No |
gator browse [limit] |
Show recent posts from followed feeds; defaults to 2 | Yes |
Durations use Go syntax, for example 30s, 1m, or 1h. The agg command fetches feeds across all users, not just the current user.
Database Schema
erDiagram
USERS ||--o{ FEEDS : owns
USERS ||--o{ FEED_FOLLOWS : follows
FEEDS ||--o{ FEED_FOLLOWS : "followed by"
FEEDS ||--o{ POSTS : contains
users— registered accounts with unique namesfeeds— RSS feeds, each owned by a user, with a nullablelast_fetched_atfeed_follows— many-to-many follows between users and feedsposts— fetched articles with a unique URL for deduplication
The full schema lives in sql/schema, and the queries used to generate the Go database layer live in sql/queries.
Development
Clone the repository and run Gator with go run:
git clone https://github.com/3mmarx/gator.git
cd gator
go run . register ammar
go run . addfeed "Hacker News" "https://news.ycombinator.com/rss"
go run . following
go run . is for development only. It compiles and runs the program on every invocation. For production, build a binary:
go build -o gator .
./gator browse 10
Regenerating the database layer
The internal/database package is generated by sqlc. After changing files in sql/queries or sql/schema, install sqlc and regenerate:
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
sqlc generate
Checks
go build ./...
go vet ./...
Project structure
.
├── main.go # Entry point and command registration
├── commands.go # Command type, registry, and dispatch
├── middleware_loggedin.go # Resolves the current user for protected commands
├── rss_feed.go # HTTP fetching and RSS/XML parsing
├── handler_*.go # One handler per CLI command
├── internal/
│ ├── config/ # ~/.gatorconfig.json handling
│ └── database/ # sqlc-generated database code
├── sql/
│ ├── schema/ # Goose migrations
│ └── queries/ # SQL queries used by sqlc
├── sqlc.yaml
├── go.mod
└── go.sum
How It Works
- On startup,
main.goreads~/.gatorconfig.json, opens a PostgreSQL connection, and registers every command in a handler map. - Commands that need a user are wrapped in
middlewareLoggedIn, which loads the user named bycurrent_user_name. gator aggselects the feed with the oldestlast_fetched_at(or one that has never been fetched), marks it as fetched, downloads its RSS XML, and inserts each item as a post.- Post URLs are unique, so running the aggregator repeatedly does not create duplicates.
gator browsejoinspostswithfeed_followsfor the current user and returns the newest posts first.
Troubleshooting
| Problem | Fix |
|---|---|
Failed to read the config file |
Create ~/.gatorconfig.json as described in Configuration. |
connect: connection refused |
Start PostgreSQL, for example brew services start postgresql@18 or sudo service postgresql start. |
pq: database "gator" does not exist |
Run createdb gator. |
pq: relation "users" does not exist |
Apply the migrations with Goose. |
zsh: command not found: gator |
Add $(go env GOPATH)/bin to your PATH. |
failed to login with the user: user not found |
Register the user first with gator register <username>. |
command not found from Gator |
Check the command name against the command reference. |
no following feeds or no posts from browse |
Add or follow feeds, then run gator agg for a while. |
duplicate key value violates unique constraint when adding a feed |
The URL is already registered. Use gator follow <url> instead. |
Acknowledgements
Gator was built as part of the Build a Blog Aggregator in Go course on Boot.dev.
License
This project is licensed under the MIT License. See LICENSE for details.
Documentation
¶
There is no documentation for this package.