DataBase

ニュースアプリをOSSで作ってみた話

GitHubはこちら → https://github.com/Yuusuke9228/news_app


目次

第1章:既存のニュースアプリに感じていた“漠然とした違和感”

「ニュースアプリを自作しました」と聞くと、よく言われます。

え?わざわざ?SmartNewsとかYahooニュースとかあるじゃん?

…もちろん知ってます。超高機能。データ分析すごい。見やすい。AIレコメンドまである。
でも、ちょっと待ってほしい。

  • 開くと動画広告が自動再生される…
  • スワイプするたびに広告が挟まる…
  • AIが“これおすすめ!”って出してくるけど別に読みたくない…
  • フィードが重くて読み込みに時間かかる…

そう、重い・うるさい・思い通りにカスタムできない
そんな”不満のかたまり”に気づいたとき、思ったんです。

「いっそ、自分で作った方がいいんじゃね?」


第2章:このアプリの特徴は「軽い・早い・静か・広がる」

● 軽い

フレームワークを使わず、素のPHP / JavaScript / CSSで構成。
余計なライブラリや読み込みがないので1秒以内で表示
しかもスマホ対応済み。

● 早い

MySQLを使ってもクエリは最小限。
SQLチューニング不要で、無限スクロールがサクサク
DB設計も最適化済み。

● 静か

広告は一切なし。通知もなし。ポップアップもなし。
「読む」ことに集中できるミニマル設計。

● 広がる

GitHubでOSS公開中。
PHP・JS・CSS・Pythonで構成されてるから拡張・改造・連携が自由

「作って終わり」じゃなく、「作ってからが始まり」。


第3章:アーキテクチャ全体像

下記が本アプリの構成図です。

╔═════════════════════════════╗
║        フロントエンド       ║ ← UI+JS+非同期通信
╚═══════╦══════════╦══════════╝
        ▼          ▼
╔═════════════════════════════╗
║        PHP API(api.php)   ║ ← ビジネスロジック/REST通信
╚════════════════╦═══════════╝
                 ▼
╔═════════════════════════════╗
║       MariaDB(MySQL互換)  ║ ← 記事/ユーザー/履歴/カテゴリ管理
╚═════════════════════════════╝
        ▲
╔═════════════════════════════╗
║  Pythonスクリプト(fetch_articles.py) ║ ← RSSデータ収集・整形・登録
╚═════════════════════════════╝

1. データ収集レイヤー(Python)

  • fetch_articles.py
    • はてなブックマークからRSS形式で人気記事を取得
    • 記事メタ情報(タイトル、URL、サムネイル、説明文)を抽出
    • カテゴリごとに振り分けてDBへ保存
    • cron等で1時間ごとなどの定期実行を想定

2. データストアレイヤー(MySQL)

本アプリケーションではMySQLを使用しましたが、SqliteやPostgresなどでも利用可能です・

🗂 テーブル構成

テーブル名目的
usersユーザー情報の管理(登録ユーザー/ゲスト)
articles記事情報の保存(RSSから取得)
categoriesカテゴリー定義の管理
article_categories記事とカテゴリーの関連付け
user_category_preferencesユーザーのカテゴリー表示設定
user_article_history記事閲覧履歴の保存
user_custom_categoriesユーザー定義の独自カテゴリ

    3. バックエンドレイヤー(PHP API)

    • ファイル:api.php
      • REST形式のAPIエンドポイント群
      • PDOを用いた安全なDB接続・操作
      • JSON形式でデータ返却
      • 各アクションに応じてビジネスロジックを呼び出し
      • ユーザー認証・セッション管理
      • エラーハンドリング、入力検証付き

    4. フロントエンドレイヤー(HTML + JS + CSS)

    UI構築:

    • index.php によるHTML構造
    • style.css によるスタイリング
    • スマホ対応済(メディアクエリ)

    コントローラー(main.js):

    • fetch API による API 呼び出し
    • DOM構築とレンダリング制御
    • カテゴリ切り替えや無限スクロール実装
    • ユーザーの設定・履歴・フィルタ情報を保持・管理

    🔸 クロスカッティングな仕組み

    ✅ 認証・セッション

    • セッション + Cookie ベースのユーザー管理
    • ゲストと登録ユーザーを自動判定
    • セッションタイムアウト処理も導入可能

    ✅ ログ管理

    • サーバーサイドにログファイル出力機能を実装可
    • Pythonログ(データ取得ログ)、PHPログ(APIアクセス履歴)を記録

    ✅ アプリ設定

    • config.php などでDB接続/RSSソース切り替え/デバッグON/OFFなど管理

    🔁 通信フロー(リクエスト〜表示まで)

    1. ユーザーリクエスト:
      • ユーザーが index.php にアクセス
      • HTML/CSS/JS をロード
    2. 初期データ取得:
      • main.js により api.php?action=init が呼び出され、カテゴリ・初期記事を取得
    3. 記事描画:
      • JSがレスポンスを受け取り、記事一覧を構築(HTML生成+差し込み)
    4. 操作とイベント処理:
      • ユーザーがスクロールやカテゴリをクリック
      • 非同期で追加読み込み(fetch
    5. 履歴保存:
      • 記事をクリックすると save_history.php にPOST
      • サーバーで user_article_history に保存

    🌀 非同期処理とUX向上の工夫

    • async/await を活用した自然なデータ取得
    • スクロール最下部で追加読み込み(無限スクロール)
    • 操作中も処理をブロックしないUX設計
    • 画面更新は最小限(DOM差し替えのみ)

    ✅ 拡張性のある設計

    このアーキテクチャの最大のメリットは**「切り離し可能な構成」**です。

    • バックエンドだけLaravelやFastAPIに差し替える
    • フロントエンドをVue.jsやReactに置き換える
    • データ収集をScrapyなどに変える
    • PostgreSQLやSQLiteにも対応可能

    また、RSSソースを別サイトに変えたり、YouTubeのAPIを使って動画ニュースを統合するといった発展も簡単です。


    第4章:使用技術スタックと構成ファイル解説

    ■ 使用言語とツール一覧

    領域使用技術
    フロントHTML / CSS / JavaScript(純粋)
    バックエンドPHP(データ取得、認証など)
    バッチ処理Python(記事の取得・パース・DB登録)
    データベースMySQL(ユーザー情報・履歴・記事保存)
    UI設計メディアクエリベースのCSS(スマホ対応)
    その他GitHub Pages / XAMPP / ローカル実行対応

    ■ ファイル構成(プロジェクトルート)

    コピーnews_app/
    │
    ├── index.php                  # メインページ(HTML/フロントエンド表示)
    ├── api.php                    # バックエンドAPI(リクエスト処理)
    ├── config.php                 # PHP設定(DB接続情報など)
    ├── config.ini                 # Python設定ファイル(記事取得用)
    ├── .htaccess                  # Apacheサーバー設定ファイル
    │
    ├── js/
    │   └── main.js                # フロントエンドJavaScript処理
    │
    ├── images/
    │   └── no-image.png           # サムネイル未設定時の代替画像
    │
    ├── css/
    │   └── style.css              # スタイルシート(index.phpに埋め込み)
    │
    ├── scripts/
    │   └── fetch_articles.py      # はてなブックマーク記事取得スクリプト
    │
    └── logs/                      # ログディレクトリ
        ├── app.log                # アプリケーションログ
        └── php_errors.log         # PHPエラーログ

    ■ フロントエンド関連

    1. index.php

    • 役割:ユーザーが最初にアクセスする、ニュースアプリのUI全体を構成する中心ファイル。
    • 主な機能
      • HTML・CSS・JavaScriptの基本構造を定義
      • 記事カードの一覧表示
      • カテゴリータブ、閲覧履歴モーダルの設計
      • メディアクエリによるスマホ対応レスポンシブデザイン

    2. js/main.js

    • 役割:ニュースの読み込み・UI操作・履歴管理など、動的挙動すべてを司るJSファイル。
    • 主な処理
      • REST APIとの通信(fetch)
      • DOM操作(記事レンダリング、カテゴリ切り替えなど)
      • イベントハンドラ(クリック、スクロール、モーダル開閉)
      • ユーザー設定の保存(localStorage)
      • 記事閲覧履歴の記録と表示

    ■ バックエンド関連

    3. api.php

    • 役割:フロントエンドとデータベースの橋渡しを行うRESTful APIハブ
    • 主な機能
      • ユーザー登録/ログイン/ログアウト(セッション管理)
      • 記事一覧・カテゴリー取得、履歴の保存・取得
      • GET / POST に応じた分岐で各種操作に対応
      • JSON形式でフロントにレスポンスを返す

    4. config.php

    • 役割:アプリ全体の設定ファイル
    • 主な内容
      • データベース接続情報(ホスト名、ユーザー名、パスワードなど)
      • アプリのルートパス、タイムゾーン設定
      • エラー出力の有効化(開発用)
      • セキュリティ上の定数設定(例:CSRFトークンなど)

    5. scripts/fetch_articles.py

    • 役割Python製のRSS収集バッチスクリプト
    • 主な処理
      • はてなブックマークのRSSを定期取得
      • 記事タイトル、URL、サムネイル、コメント数等をパース
      • カテゴリー分類ロジックに従ってMySQLに保存
      • cronやタスクスケジューラで定期実行可能

    ■ サーバー設定

    6. .htaccess
    • 役割:Apache用設定ファイル
    • 主な用途
      • サブディレクトリでの正しいルーティング処理
      • 404ページや403ページのカスタム設定
      • ファイルのキャッシュ設定
      • セキュリティ(ディレクトリ一覧の非表示など)

    主要機能の具体的な実装構造

    次に、アプリが提供する5つの機能について、裏側の処理フローも含めて紹介します。


    1. 記事表示機能

    • 処理の流れ
      • api.phpgetArticles()関数で記事データを取得
      • main.jsloadArticles()がAPIを叩いてJSON受け取り
      • renderArticles()関数でDOMにニュースを出力(カード形式)

    2. カテゴリー管理機能

    • 処理の流れ
      • api.phpgetCategories()でカテゴリ一覧取得
      • ユーザーの選好はupdateCategoryPreferences()で保存
      • フロント側はrenderCategoryTabs()でUI表示

    3. ユーザー管理機能(登録・認証・セッション)

    • 処理の流れ
      • ユーザー登録 → registerUser()(DBにINSERT)
      • ログイン → loginUser()(セッション発行)
      • ログアウト → logoutUser()(セッション破棄)
    • ゲストユーザーは制限付きで使用可能(履歴非保存など)

    4. 閲覧履歴機能

    • 保存
      • 記事カードのクリックイベントで、main.jssaveArticleHistory()をAjax送信
    • 取得・表示
      • getArticleHistory()で該当ユーザーの履歴を取得
      • モーダル内にレンダリング(最近見た記事一覧)

    5. 記事収集処理(RSS)

    • 使用スクリプトscripts/fetch_articles.py
    • フロー
      1. RSS XMLを取得(requestsライブラリ)
      2. 各記事のタイトル、リンク、概要、画像URLを抽出
      3. テキスト分類ロジックでカテゴリ分類
      4. MySQLに挿入(既存チェックあり)

    補足:すべての機能は**「最小構成」+「最大拡張性」**で作られている

    このプロジェクトの方針はシンプルです。

    「今使える最小構成でまず動かし、必要に応じてガンガン拡張する」

    だからこそ、

    • 初心者はそのまま使える
    • 中級者は改造して楽しめる
    • 上級者は再構築して活用できる

    そんな“育てられるOSS”に育っています。


    第5章:主な機能と実装方法

    【DB構造】

    -- ユーザーテーブル
    CREATE TABLE users (
        id INT AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(50) UNIQUE,
        password VARCHAR(255),
        -- ハッシュ化されたパスワード
        email VARCHAR(100) UNIQUE,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        last_login TIMESTAMP NULL,
        is_guest BOOLEAN DEFAULT FALSE
    );
    
    -- カテゴリーテーブル
    CREATE TABLE categories (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(50) NOT NULL,
        slug VARCHAR(50) NOT NULL UNIQUE,
        is_default BOOLEAN DEFAULT FALSE,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    
    -- 記事テーブル
    CREATE TABLE articles (
        id INT AUTO_INCREMENT PRIMARY KEY,
        title VARCHAR(255) NOT NULL,
        url VARCHAR(255) NOT NULL UNIQUE,
        description TEXT,
        thumbnail_url VARCHAR(255),
        source_site VARCHAR(100),
        bookmark_count INT DEFAULT 0,
        published_at TIMESTAMP,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    
    -- 記事とカテゴリーの関連付けテーブル
    CREATE TABLE article_categories (
        article_id INT,
        category_id INT,
        PRIMARY KEY (article_id, category_id),
        FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
        FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
    );
    
    -- ユーザーのカテゴリー設定テーブル
    CREATE TABLE user_category_preferences (
        user_id INT,
        category_id INT,
        is_visible BOOLEAN DEFAULT TRUE,
        display_order INT DEFAULT 0,
        PRIMARY KEY (user_id, category_id),
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
        FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
    );
    
    -- ユーザーの記事閲覧履歴テーブル
    CREATE TABLE user_article_history (
        user_id INT,
        article_id INT,
        viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (user_id, article_id),
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
        FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE
    );
    
    -- ユーザーのカスタムカテゴリーテーブル
    CREATE TABLE user_custom_categories (
        id INT AUTO_INCREMENT PRIMARY KEY,
        user_id INT,
        name VARCHAR(50) NOT NULL,
        display_order INT DEFAULT 0,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
    );
    
    -- デフォルトカテゴリーの作成
    INSERT INTO
        categories (name, slug, is_default)
    VALUES
        ('総合', 'general', TRUE),
        ('テクノロジー', 'technology', TRUE),
        ('エンタメ', 'entertainment', TRUE),
        ('ビジネス', 'business', TRUE),
        ('スポーツ', 'sports', TRUE),
        ('科学', 'science', TRUE),
        ('健康', 'health', TRUE),
        ('ライフスタイル', 'lifestyle', TRUE);

    5-1. ユーザー登録・ログイン機能(PHP+MySQL)

    【ポイント】

    • セッションベースでログイン管理
    • パスワードはpassword_hash()で暗号化
    • 登録・ログイン時に簡単なバリデーションを実装
    
    

    【処理概要】

    • api.php:ユーザー名+パスワードを受け取って登録
    • api.php:認証チェックしてセッション発行
    • api.php:ログイン済みでなければリダイレクト

    5-2. 閲覧履歴保存と取得(MySQL+Ajax)

    【ポイント】

    • ユーザーがニュースをクリックした時点でAjax送信
    • save_history.phpがDBに記事タイトル・URL・時刻を保存

    【クライアント側(JS)】

    document.querySelectorAll('.news-link').forEach(link => {
      link.addEventListener('click', e => {
        fetch('save_history.php', {
          method: 'POST',
          body: new URLSearchParams({
            title: link.dataset.title,
            url: link.href
          })
        });
      });
    });


    5-3. 無限スクロール(JS)

    【ポイント】

    • IntersectionObserver API を活用して、スクロール最下部で自動取得
    • ページングパラメータを使い、同じPythonスクリプトを何度も叩く

    【JSコード例】

    let page = 1;
    let observer = new IntersectionObserver(entries => {
      if (entries[0].isIntersecting) {
        fetch(`get_feed.py?page=${page++}`)
          .then(res => res.json())
          .then(addToDOM);
      }
    });
    
    observer.observe(document.querySelector('#loadTrigger'));


    5-4. スマホ対応レイアウト(CSS)

    【ポイント】

    • メディアクエリのみで対応(CSSフレームワーク不使用)
    • 画面幅600px以下でフォントやマージンを調整

    【CSS例】

    @media (max-width: 600px) {
      .news-container {
        padding: 10px;
        font-size: 0.9em;
      }
    }



    第6章:カスタマイズ方法の紹介

    このアプリの魅力のひとつは、**とことん「改造しやすい」**という点です。
    フレームワークを使っていないので、特定の規約に縛られることもありません。
    ここでは、よくあるカスタマイズの例を紹介します。


    ■ 6-1. 表示するRSSフィードを変える

    RSSの取得は fetch_articles.py で行っているため、ここに任意のRSS URLを追加すればOKです。

    # 人気記事を取得するカテゴリのリスト
    categories = [
        {"name": "総合", "url": "https://b.hatena.ne.jp/hotentry.rss"},
        {"name": "テクノロジー", "url": "https://b.hatena.ne.jp/hotentry/it.rss"},
        {"name": "エンタメ", "url": "https://b.hatena.ne.jp/hotentry/entertainment.rss"},
        {"name": "ビジネス", "url": "https://b.hatena.ne.jp/hotentry/economics.rss"},
        {"name": "スポーツ", "url": "https://b.hatena.ne.jp/hotentry/game.rss"},
        {"name": "科学", "url": "https://b.hatena.ne.jp/hotentry/knowledge.rss"},
        {"name": "健康", "url": "https://b.hatena.ne.jp/hotentry/life.rss"},
        {"name": "ライフスタイル", "url": "https://b.hatena.ne.jp/hotentry/guide.rss"}  # guide.rssに変更
    ]
    
    
    

    企業内イントラのRSS、業界特化のメディアなどももちろんOK。
    「自分の情報源だけに絞ったニュースアプリ」がすぐ完成します。


    ■ 6-2. UIテーマを自分好みに変更

    style.css は完全に自由設計。たとえば「ダークモード化」は以下のようにできます。

    body {
      background-color: #121212;
      color: #f1f1f1;
    }
    
    a {
      color: #ff9800;
    }
    
    
    

    CSS変数やテーマ切替機能を追加すれば、ダーク/ライトもボタン一つで切り替え可能になります。


    第7章:GitHubでの導入方法とコントリビュートの呼びかけ

    ■ クローン方法(ローカル実行)

    git clone https://github.com/Yuusuke9228/news_app.git
    cd news_app
    php -S localhost:8080
    # または
    python3 -m http.server 8080
    

    その後、ブラウザで http://localhost:8080/index.php にアクセスすれば即動作。

    MySQLを有効にしたい場合は、db/ のテーブル定義をインポートしてください。


    ■ PR・Issue歓迎!

    バグ報告・機能リクエスト・改善提案など、どんな小さなことでも大歓迎です。

    例:

    • RSS更新間隔を変えたい
    • モバイルのUIを改善したい
    • ログインをJWTで書き直したい
    • 外部APIと連携したい(ChatGPT等)

    → ぜひGitHubリポジトリにアクセスして、Star・Fork・PRをお願いします!


    第8章:今後の課題とアップデート構想

    需要がありそうなら下記を実装しようかと考えています。
    下記に挙げた内容はどれも即、実装できるくらい簡単な内容で1日もあれば全て実装できます。

    ● 課題

    • RSS取得の失敗時のハンドリングが甘い
    • ダークモード未対応
    • 複数ユーザーでの同時アクセス時の挙動検証不足
    • セキュリティ面(XSS対策など)も今後強化必要

    ● やりたいこと

    • タグ/カテゴリ別フィルタリング
    • お気に入り/ブックマーク機能
    • 閲覧傾向によるパーソナライズ
    • 動画ニュース対応(YouTube埋め込みなど)
    • 通知機能(Web Push APIなど)

    どれも「拡張性がある設計」にしてあるので、少しずつ育てていく予定です。


    第9章:まとめ 〜ニュースは”自分で選ぶ時代”へ〜

    このアプリは、以下のような人におすすめです:

    • 大手ニュースアプリに疲れている人
    • 情報を「自分で選びたい」と思っている人
    • 軽くて速いニュースアプリを探している人
    • OSSで学びたい・貢献したい人
    • 技術学習の一環で実用的なサンプルが欲しい人

    「ニュースを見る」から「ニュースアプリを育てる」へ。

    あなたもぜひ、自分だけのニュースアプリを作ってみませんか?


    ▶ GitHubはこちら

    https://github.com/Yuusuke9228/news_app

    StarやFork、Issueの投稿お待ちしています!