Laravel11とReact でインスタグラム分析サイト作成


## プロジェクト概要

普段は電気系エンジニアとして働いていますが、趣味として1年ほど前からプログラミングを始めました。このWebアプリは個人的に初めて開発したものです:nerd:

今回は主に、LaravelとReactの設定関係に焦点を当てています。

**フィードバック歓迎**  
改善点や気になる点があれば、ぜひコメントで教えていただけると幸いです。今回紹介する内容が、他の方の参考になればと思います。

[インスタグラム ハッシュタグ分析サイト](https://trend-tagger.com/)はこちらで公開しています。  
→ **機能やサイトの詳細は以下をご覧ください。**

---

## 機能要件

このWebアプリでは、Instagram APIを活用してハッシュタグに基づくデータを収集し、分析を行います。以下に主な機能をまとめました。

1. **Instagram APIの利用**  
   指定したハッシュタグ（最大20個）に関連する投稿を毎時間取得し、1つのハッシュタグにつき1000～3000件の投稿を取得します。

2. **データの表示**  
   取得した投稿から、次のようなデータを表示します：
   - **いいね数の多い投稿トップ10**  
     任意のハッシュタグで検索可能。  
     [トップ投稿を見る](https://trend-tagger.com/)
   - **投稿数・いいね数の折れ線グラフ**  
     "日にちごと"、"時間ごと"に表示し、任意のハッシュタグで検索可能です。  
     [グラフを見る](https://trend-tagger.com/analytics)
   - **関連ハッシュタグの円グラフ**  
     任意のハッシュタグと一緒に使われるハッシュタグを表示します。  
     [関連タグを見る](https://trend-tagger.com/popular-hashtags)

3. **ユーザーの閲覧制限**  
   ゲストユーザーは主要なハッシュタグのみ閲覧可能。ログイン済みユーザーは全てのハッシュタグを閲覧可能（有料プランは無し）。

---
<br>

## 環境

### フロントエンド
- **ホスティング**: Netlify
- **言語**: React

### サーバーサイド
- **ホスティング**: エックスサーバー
- **言語**: Laravel11
- **データベース**: MySQL

### その他
- **バージョン管理**: GitHub
- **ローカル開発**: Docker

---
<br>

## ドメイン設定

このアプリは「**お名前.com**」で取得したドメイン「trend-tagger.com」を使用しています。設定は以下の通りです。

### Netlify側 (メインドメイン)

| ホスト名              | 種別  | 内容                       | TTL  | 優先度 |
|------------------------|-------|------------------------------|------|--------|
| api.trend-tagger.com    | A     | エックスサーバー側のIPアドレス | 3600 | 0      |

:::note info
あらかじめNetlifyにドメインを登録する必要があります。参考記事はこちら: [Netlifyドメイン登録](https://takayyz.com/blog/configure-customdomain-on-netlify/)
:::

### エックスサーバー側 (サブドメイン)

| ホスト名              | 種別  | 内容                         | TTL  | 優先度 |
|------------------------|-------|------------------------------|------|--------|
| api.trend-tagger.com    | A     | エックスサーバー側のIPアドレス | 3600 | 0      |
| www.trend-tagger.com    | CNAME | trend-tagger.netlify.app     | 3600 | 0      |

---
<br>

## サーバー側の環境構築

サーバー側ではPHPのバージョン管理や、Composerのインストールを行っています。以下に具体的な手順を記載します。

### PHPバージョン変更

:::note info
SSH接続が必要です。接続方法については[エックスサーバーSSH接続](https://hiro8blog.com/connect-to-xserver-by-ssh/)を参考にしてください。
:::

- ホームディレクトリに「bin」フォルダを作成します。

```bash
mkdir $HOME/bin
```

- シンボリックリンクを作成します（`whereis php` コマンドで存在するPHPバージョンを確認）。

```bash
ln -s /usr/bin/php8.3 $HOME/bin/php
```

- vimを開く

```bash
vi ~/.bashrc
```

- 開いたvimの最終行に追加(:wで上書き、:qで終了)
```bash
export PATH=$HOME/bin:$PATH
```

- 変更内容を反映
```bash
source ~/.bashrc
```
---
### Composerのインストール

- SSH接続後に、以下のコードを使ってComposerをインストールします。  
[Composer公式サイト](https://getcomposer.org/download/)にて最新バージョンのインストールコードを確認してください。

```bash
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'your_hash_value') === 'your_hash_value') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"
```

---
<br>

## Laravel設定抜粋

Laravel11とフロントエンドとの認証設定について、特に困った点をまとめました。
これらの設定は、Laravel11でのAPI認証では必須かと思います。

:::note warn
Kernel.phpというファイルは存在しないので注意が必要です
(記事を調べるとよく出てきますが、過去のLaravelバージョンで使用されていたようです)
:::




### config > cors.php

```php
<?php

return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['https://trend-tagger.com'],
    'allowed_headers' => ['*'],
    'supports_credentials' => true,
];
```

### bootstrap > app.php

```php
<?php

use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php'
    )
    ->withMiddleware(function ($middleware) {
        $middleware->statefulApi(); // この記述が重要です！
    })
    ->create();
```

### routes > api.php の抜粋
```php
// ユーザー登録処理へのルート
Route::post('/register', [RegisterController::class, 'register']);

// フロントエンドから人気投稿のリクエスト要求された時に返すためのルート
Route::get('/top-posts/secure/{hashtag}', [PostController::class, 'getTopPostsByHashtag'])->middleware('auth:sanctum');
```
:::note info
middleware('auth:sanctum') この記述によって、フロントエンドとサーバーサイドとのユーザー認証を行っているようです。
:::

---
<br>

## React記述抜粋

### src > components > auth > Register.js (ユーザー登録処理)

```javascript
import React, { useState } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import GoogleLoginButton from "./GoogleLoginButton";
import { getCookie } from "../../utils/cookies";

const Register = () => {
  const [username, setUsername] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [passwordConfirmation, setPasswordConfirmation] = useState("");
  const [errors, setErrors] = useState({});
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();

    const csrfToken = getCookie("XSRF-TOKEN");

    try {
      await axios.post(
        "/api/register",
        { username, email, password, password_confirmation: passwordConfirmation },
        { headers: { "X-XSRF-TOKEN": csrfToken } }
      );
      localStorage.setItem("user", JSON.stringify({ username }));
      navigate("/success");
    } catch (error) {
      setErrors(error.response?.data?.errors || {});
    }
  };

  return (
    <div>
      <h2>新規登録</h2>
      <form onSubmit={handleSubmit}>
        <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} required />
        <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
        <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
        <input type="password" value={passwordConfirmation} onChange={(e) => setPasswordConfirmation(e.target.value)} required />
        {Object.keys(errors).length > 0 && <div>{Object.values(errors).map((error, index) => <p key={index}>{error}</p>)}</div>}
        <button type="submit">登録</button>
      </form>
      <GoogleLoginButton />
    </div>
  );
};

export default Register;
```

:::note info
`withXSRFToken: true` の記述がないと、認証に失敗します。
:::

---

## 最後に

今回はLaravel11とReactを使って開発したアプリについて、設定面を中心に記載しました。気になる部分やご意見があれば、ぜひコメントいただけると嬉しいです。
リクエストなどあれば、処理側のコードも紹介したいと思います:grinning:

---

## 参考
https://takayyz.com/blog/configure-customdomain-on-netlify/
https://hiro8blog.com/connect-to-xserver-by-ssh/
https://qiita.com/kensukeX/items/a045c5b4ecec033425d4
https://getcomposer.org/download/