Web 上の RSS/Atom フィードを集約し、記事を時系列で読むための個人向け RSS リーダーです。購読しているサイトの新着を、広告やノイズに邪魔されず自分のペースでまとめて追えることを目指しています。Go 製の API バックエンドと Next.js 製のリーダー UI の 2 部構成で、両者は /api/v1 の HTTP API のみで疎結合に連携します。あわせて、クリーンアーキテクチャや一貫した API 設計を実践する学習プロジェクトでもあります。
⚠️ ステータス: 仮実装段階(WIP) — フロントエンド・バックエンドの主要フローは動作しますが、一部は未完です(下記「ステータス」参照)。
主要なユースケース(フィード登録 → 記事取得 → 閲覧)は通しで動作します。一方、以下は未完または暫定実装です:
- 定期取得ジョブはスタブ —
backend/internal/jobのスケジューラ枠組みはありますが、DueFeedFetcher.Execの本体は未実装です。フィードの更新は現状POST /feeds/:id/refreshの手動トリガで行います。 - 既読 / スター / 新着はフロントエンドのローカル状態 — バックエンドには永続化されません(ブラウザ側で判定・保持)。
- 認証・マルチユーザは未対応 — 単一利用を前提とした最小構成です。
- フィード登録
- フィード URL の直接登録(
POST /feeds) - Web ページ URL からのオートディスカバリ(
<link rel="alternate">を解析、POST /feeds/discover)
- フィード URL の直接登録(
- フィード管理 — 一覧(ページネーション)/ 取得 / 削除 / 手動再取得
- 記事閲覧 — 全フィード横断 / フィード別の一覧をキーセットページネーションで取得
- Signal リーダー UI — 既読・スター・新着をフロントエンドで管理する閲覧画面
| 分類 | 採用技術 |
|---|---|
| 言語 | Go 1.25 |
| Web フレームワーク | Echo v5 |
| DB / ドライバ | PostgreSQL 18 / pgx v5 |
| クエリ生成 | sqlc |
| RSS 解析 | gofeed |
| バリデーション | go-playground/validator |
| ID 採番 | google/uuid(UUIDv7) |
| ロギング | 標準 log/slog |
| テスト | 標準 go test + testcontainers-go(統合テスト) |
| 開発支援 | air(ホットリロード)/ golangci-lint |
| 分類 | 採用技術 |
|---|---|
| フレームワーク | Next.js 15.5(App Router / Turbopack) |
| UI ライブラリ | React 19 |
| データ取得 | TanStack Query |
| スキーマ検証 | Zod |
| スタイリング | Tailwind CSS v4 |
| アイコン | lucide-react |
| テスト | Vitest(ユニット)/ Playwright(E2E) |
| パッケージ管理 | pnpm(workspace) |
Docker Compose / GitHub Actions(CI)/ lefthook(Git hooks)/ CodeRabbit(自動レビュー)
前提: Docker(+ Docker Compose)、Go 1.25、Node.js + pnpm。compose はバックエンドと DB のみを起動します。フロントエンドは別途
pnpm devで起動してください。
リポジトリルートには開発用の .env(既定の開発用認証情報)が含まれています。そのまま起動できます:
docker compose up- PostgreSQL 18 と、
airによるホットリロード付きの API が起動します。 - API は
http://localhost:8080(ルートは/api/v1)で待ち受けます。 - DB スキーマは初回起動時に
backend/internal/infra/persistence/postgres/sql/schema.sqlから自動適用されます。
cd frontend
pnpm install
pnpm devhttp://localhost:3000で開きます。- 接続先 API は環境変数
NEXT_PUBLIC_API_BASE_URLで指定します(未設定時はhttp://localhost:8080)。
cd backend
# DATABASE_URL を環境に設定してから
go run ./cmd| 変数 | 必須 | 既定値 | 説明 |
|---|---|---|---|
DATABASE_URL |
✅ | — | PostgreSQL 接続文字列 |
LOG_LEVEL |
info |
debug / info / warn / error |
|
LOG_FORMAT |
text |
text / json |
|
CORS_ALLOWED_ORIGINS |
http://localhost:3000 |
許可するブラウザ Origin(カンマ区切り) |
| 変数 | 説明 |
|---|---|
DB_USER / DB_PASSWORD / DB_NAME |
Postgres コンテナの初期化に使用 |
| 変数 | 既定値 | 説明 |
|---|---|---|
NEXT_PUBLIC_API_BASE_URL |
http://localhost:8080 |
バックエンド API のベース URL |
すべてのエンドポイントは /api/v1 配下です。
| メソッド | パス | 概要 | リクエスト | 成功時 |
|---|---|---|---|---|
POST |
/feeds |
フィード URL を登録 | { "feed_url": "..." } |
201(feed, articles) |
POST |
/feeds/discover |
Web ページから feed をオートディスカバリして登録 | { "website_url": "..." } |
201(feed, articles, candidates) |
GET |
/feeds |
フィード一覧 | ?cursor=&limit=(既定 10 / 1–100) |
200(feeds) |
GET |
/feeds/:id |
フィード詳細 | — | 200(feed) |
POST |
/feeds/:id/refresh |
フィードを再取得 | — | 204 |
DELETE |
/feeds/:id |
フィード削除 | — | 204 |
GET |
/articles |
記事一覧(全フィード横断) | ?cursor=&limit= |
200(articles) |
GET |
/feeds/:feed_id/articles |
フィード別の記事一覧 | ?cursor=&limit= |
200(articles) |
登録系で受け付ける URL は
http/httpsスキームのみです。
すべてのレスポンスは封筒(envelope)で統一されています。
成功:
{
"data": { "feeds": [ /* ... */ ] },
"meta": {
"request_id": "...",
"pagination": { "next_cursor": "opaque-token", "has_more": true }
}
}meta.request_idはX-Request-IDヘッダと相関(ログ追跡用)。- 一覧系のみ
meta.paginationが付与されます。next_cursorはクライアントが中身を解釈しない不透明トークンで、nullは「次ページなし」を意味します。
エラー:
{
"error": { "code": "invalid_argument", "message": "...", "details": [ { "field": "feed_url", "reason": "..." } ] },
"meta": { "request_id": "..." }
}error.code |
HTTP ステータス |
|---|---|
not_found |
404 |
invalid_argument |
400 |
conflict |
409 |
external_unavailable |
502 |
internal |
500 |
5xx の
messageはクライアントには"internal server error"に置き換えられます(内部情報を漏らさないため)。
curl -X POST http://localhost:8080/api/v1/feeds \
-H 'Content-Type: application/json' \
-d '{"feed_url": "https://example.com/feed.xml"}'クリーンアーキテクチャに倣い、依存は内側(ドメイン)に向きます。コンポジションルートは backend/internal/di/container.go。
domain/model— エンティティ(Feed/Article/FetchStatusなど、ID は UUIDv7)domain/repository— リポジトリインターフェース(実装は infra 側)usecase— インタラクタ。HTTP も SQL も知らないhandler— Echo ハンドラ。入力検証して usecase に委譲、apperrorを返すinfra/—gateway(gofeed + HTTP クライアント)/persistence(sqlc + リポジトリ)/middleware(エラーハンドラ等)/router/config/loggerjob— 定期取得スケジューラ(本体はスタブ。上記「ステータス」参照)
詳細な設計は親ワークツリーの
../docs/internal/arch.md/../docs/internal/directory.md、コーディング規約・トランザクション・エラー戦略はCLAUDE.mdを参照してください(README は入口、詳細は各ドキュメントに委譲)。
| テーブル | 役割 | 主な制約 |
|---|---|---|
feeds |
登録フィード | feed_url / website_url は UNIQUE、PK は UUID |
articles |
記事 | UNIQUE(feed_id, external_id)、feed_id は feeds への外部キー(ON DELETE CASCADE) |
feed_fetch_status |
フィードごとの取得状態(ETag / Last-Modified / 次回取得予定など) | PK は feed_id |
スキーマ定義: backend/internal/infra/persistence/postgres/sql/schema.sql
go-rss-reader/
├── backend/ # Go API(module: rss_reader)
│ ├── cmd/ # エントリポイント(main)
│ └── internal/
│ ├── domain/{model,repository}
│ ├── usecase/
│ ├── handler/ # Echo ハンドラ
│ ├── infra/{gateway,persistence,middleware,router,config,logger}
│ ├── di/ # コンポジションルート
│ ├── job/ # 定期取得スケジューラ(WIP)
│ └── apperror/ applog/ apiresp/
├── frontend/ # Next.js リーダー UI
│ └── src/
│ ├── app/ # App Router(layout / page / providers ...)
│ ├── components/reader/ # リーダー UI コンポーネント
│ └── lib/{api}/ # API クライアント / フック / ストア
├── .github/ # GitHub Actions(CI)
├── docker-compose.yaml # backend + Postgres(frontend は含まない)
├── CLAUDE.md # 開発規約(正典)
└── LICENSE
go test ./... # テスト(-race 推奨: go test -race ./...)
golangci-lint run # Lint
golangci-lint fmt # フォーマット
sqlc generate # sql/*.sql 変更後にコード再生成pnpm test # Vitest(ユニット)
pnpm test:e2e # Playwright(E2E、初回は pnpm test:e2e:install)
pnpm lint # ESLint
pnpm typecheck # tsc --noEmit
pnpm build # 本番ビルド詳しいビルド/テスト方針・規約は CLAUDE.md を参照してください。
MIT License © 2026 55408suga