【ChatGPT開発入門】FastAPIとNext.jsで株価データを取得し、グラフを表示する🎵

この記事では、PythonのFastAPIを使用してYahoo Financeから株価データを取得し、そのデータをNext.jsを使って表示する方法について解説します。また、実装における工夫点についても詳しく説明します。

## プロジェクトの概要

FastAPIを用いてYahoo Financeから株価データを取得し、Next.jsを使ってそのデータを表示するウェブアプリケーションを作成します。以下が今回の実装における主なステップです。

1. **FastAPIサーバーのセットアップ**
2. **Next.jsアプリケーションのセットアップ**
3. **FastAPIとNext.jsの統合**
4. **データのグラフ化**

### ディレクトリ構成

まず、プロジェクト全体のディレクトリ構成を以下のように整理します。

```
my-app/
├── back-end/
│   ├── app/
│   │   ├── __init__.py
│   │   ├── main.py
│   └── requirements.txt
├── my-next-app/
│   ├── pages/
│   │   ├── index.js
│   ├── public/
│   ├── styles/
│   ├── .gitignore
│   ├── package.json
│   ├── README.md
│   ├── next.config.js
└── README.md
```

### 各ディレクトリとファイルの説明

- **back-end/**: FastAPIプロジェクトを含むディレクトリ。
  - **app/**: FastAPIアプリケーションのコードを格納するディレクトリ。
    - **__init__.py**: パッケージとして認識させるための空ファイル。
    - **main.py**: FastAPIのメインアプリケーションコード。
  - **requirements.txt**: 必要なPythonパッケージを記載したファイル。
```requirements.txt
fastapi
uvicorn
yahoo_fin
```

- **/**: Next.jsプロジェクトを含むディレクトリ。
  - **pages/**: Next.jsのページコンポーネントを格納するディレクトリ。
    - **index.js**: メインページのコンポーネント。
  - **public/**: 静的ファイルを配置するディレクトリ。
  - **styles/**: CSSファイルを配置するディレクトリ。
  - **.gitignore**: Gitが無視するファイルやディレクトリを記述したファイル。
  - **package.json**: Next.jsプロジェクトの設定ファイル。
  - **README.md**: プロジェクトの説明ファイル。
  - **next.config.js**: Next.jsの設定ファイル（必要に応じて）。

## 1. FastAPIサーバーのセットアップ

### インストールと初期設定

まず、必要なパッケージをインストールします。

```bash
pip install fastapi uvicorn yahoo_fin
```

次に、FastAPIサーバーのコードを以下のように作成します。

```python
# app/main.py

from fastapi import FastAPI, HTTPException
from yahoo_fin import stock_info as si
from fastapi.middleware.cors import CORSMiddleware
import pandas as pd
import logging

app = FastAPI()

origins = [
    "http://localhost:3000",  # Next.jsのデフォルトのポート
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@app.get("/stocks/{symbol}")
async def get_stock_data(symbol: str, start: str, end: str):
    try:
        logger.info(f"Fetching data for {symbol} from {start} to {end}")
        data = si.get_data(symbol, start_date=start, end_date=end)
        data = data.reset_index()
        data['date'] = data['index'].astype(str)
        return data.to_dict(orient='records')
    except Exception as e:
        logger.error(f"Error fetching data: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail=str(e))
```

### 工夫した点

- **エラーハンドリング**：適切なエラーハンドリングを追加し、エラーが発生した場合に詳細なログを記録するようにしました。
- **データ形式の整形**：`pandas`データフレームのインデックスをリセットし、`date` 列を作成することで、データが正しい形式で返されるようにしました。

## 2. Next.jsアプリケーションのセットアップ

### インストールと初期設定

Next.jsプロジェクトを作成し、必要なパッケージをインストールします。

```bash
npx create-next-app my-next-app
cd my-next-app
npm install chart.js react-chartjs-2
```

次に、Next.jsアプリケーションのコードを以下のように作成します。

```javascript
import { useState } from 'react';
import { Line } from 'react-chartjs-2';
import { Chart, registerables } from 'chart.js';

Chart.register(...registerables);

export default function Home() {
  const [data, setData] = useState(null);
  const [symbol, setSymbol] = useState('');
  const [start, setStart] = useState('');
  const [end, setEnd] = useState('');

  const fetchData = async () => {
    try {
      const response = await fetch(`http://localhost:8000/stocks/${symbol}?start=${start}&end=${end}`);
      const result = await response.json();

      // データのコンソール出力
      console.log('Fetched data:', result);

      if (Array.isArray(result)) {
        setData(result);
      } else {
        console.error('Unexpected data format:', result);
        setData([]);
      }
    } catch (error) {
      console.error('Error fetching data:', error);
      setData([]);
    }
  };

  const chartData = {
    labels: data ? data.map(item => item.date) : [],
    datasets: [
      {
        label: `${symbol} Stock Price`,
        data: data ? data.map(item => item.close) : [],
        fill: false,
        backgroundColor: 'rgba(75,192,192,0.4)',
        borderColor: 'rgba(75,192,192,1)',
      },
    ],
  };

  return (
    <div>
      <h1>Stock Data</h1>
      <input placeholder="Symbol" value={symbol} onChange={(e) => setSymbol(e.target.value)} />
      <input type="date" value={start} onChange={(e) => setStart(e.target.value)} />
      <input type="date" value={end} onChange={(e) => setEnd(e.target.value)} />
      <button onClick={fetchData}>Fetch Data</button>
      {data && (
        <Line data={chartData} />
      )}
    </div>
  );
}
```

### 工夫した点

- **データの形式確認**：データを取得した際に、データが配列形式であることを確認し、想定外のデータ形式が返された場合のエラーハンドリングを追加しました。
- **グラフの表示**：`chart.js` と `react-chartjs-2` を使用して、取得したデータをグラフ化しました。

## 3. FastAPIとNext.jsの統合

FastAPIサーバーを起動し、Next.jsアプリケーションからデータを取得して表示します。

### 実行

1. **FastAPIサーバーを起動**：
   ```bash
   uvicorn app.main:app --reload
   ```

2. **Next.jsアプリケーションを起動**：
   ```bash
   npm run dev
   ```

ブラウザで `http://localhost:3000` にアクセスすると、Yahoo Financeから取得したデータがグラフとして表示されます。

### シンプルな構成が難しかった理由

最初はシンプルな構成で実装を試みましたが、以下の点で難しさがありました。

- **クランブトークンとクッキーの問題**: JavaScriptのライブラリを用いる際に、Yahoo Financeのクランブトークンとクッキーの取得や管理が必要でした。一方、Pythonの`yahoo_fin`ライブラリではこれらの管理が自動化されており、設定が簡単でした。
- **エラーハンドリングの複雑さ**: JavaScript側で発生するエラーのハンドリングが複雑で、エラーの原因を特定しにくかったため、PythonのFastAPIを使用してバックエンドを構築することにしました。

## まとめ
この記事では、FastAPIとNext.jsを使ってYahoo Financeから株価データを取得し、そのデータをグラフとして表示する方法を紹介しました。エラーハンドリングやデータの形式確認など、いくつかの工夫点を中心に解説しました。このアプローチを活用して、他のデータソースやグラフ表示にも応用できるでしょう。