A Go-based RSS feed aggregator and API that lets users create accounts, subscribe to feeds, and receive recent posts from the feeds they follow. The app periodically fetches configured RSS sources, parses the items, and stores them in PostgreSQL for later retrieval.
- Create users and generate an API key
- Authenticate requests using the
Authorization: ApiKey <key>header - Add new feeds to the system
- Follow and unfollow feeds
- Poll RSS feeds in the background and store posts
- Expose a JSON API for users and posts
- Use PostgreSQL with generated database access code
- Go
- Chi router
- PostgreSQL
- sqlc-generated database layer
- Goose SQL migrations
- RSS XML parsing with Go's standard library
main.go— application startup and router configurationscraper.go— background feed polling and RSS parsing logichandler_*.go— HTTP handlers for users, feeds, and followsmodels.go— API-facing model conversion helpersinternal/auth/— API key extraction logicinternal/database/— generated DB queries and modelssql/SCHEMA/— SQL migration filessql/queries/— SQL for generated database access
- Go 1.25+
- PostgreSQL database
- A valid
.envfile with runtime configuration
Create a .env file in the project root:
PORT=8080
DB_URL=postgres://username:password@localhost:5432/rss_scraper?sslmode=disableYou can also set these directly in your environment before running the app.
Create the PostgreSQL database and apply the migration files in sql/SCHEMA/.
If you are using Goose, the migration flow is typically:
goose upIf you use a different migration workflow, make sure all schema files are applied in sequence:
001_USERs.sql002_USERs_apikey.sql003_feeds.sql004_feeds_follows.sql005_feeds_lastfetchedat.sql006_posts.sql
go mod download
go run .The app starts an HTTP server and launches a background scraper loop using StartScraping(db, 10, time.Minute).
Base path: /v1
-
POST /v1/users- Body:
{ "name": "Alice" } - Creates a user and returns the created record with an API key.
- Body:
-
GET /v1/users- Requires
Authorization: ApiKey <api_key> - Returns the authenticated user's profile
- Requires
-
POST /v1/feeds- Requires auth
- Body:
{ "name": "Example Feed", "url": "https://example.com/feed.xml" }
-
GET /v1/feeds- Lists feeds in the system
-
POST /v1/feed_follow- Requires auth
- Body:
{ "feed_id": "<uuid>" }
-
GET /v1/feed_follow- Requires auth
- Returns the authenticated user's followed feeds
-
DELETE /v1/feed_follow/{feedFollowID}- Requires auth
- Unfollows a feed by its follow record ID
GET /v1/posts- Requires auth
- Returns up to 10 recent posts for the authenticated user
curl -X POST http://localhost:8080/v1/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice"}'curl -X GET http://localhost:8080/v1/users \
-H "Authorization: ApiKey YOUR_API_KEY"curl -X POST http://localhost:8080/v1/feeds \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Tech News","url":"https://example.com/rss"}'The background scraper loops on a timer and asks the database for the next feeds to fetch. For each feed URL it performs an HTTP GET, parses the RSS XML, and inserts posts into the posts table while skipping duplicates.
This logic lives in scraper.go and is started in main.go with:
go StartScraping(db, 10, time.Minute)- Feed URLs are required to be unique per feed record.
- Post URLs are also unique to avoid duplicates.
- The project uses
go-chifor routing and CORS and relies on environment-based configuration.
This project currently does not include a license file. Add one if you intend to distribute it publicly.