ポケポケもどきをTypeScriptで作る - (3) ポケモンのデータと画像を取得するAPIの作成

# 概要
このシリーズは TypeScript の勉強がてらに、簡易版ポケポケをWebアプリとして実装することを試みるものです。[前回 (データベース設計編)](https://qiita.com/cobieee/items/07db4ebb24e10e185cba) は、要件に基づいてデータベースの設計および MySQLによる実装を行いました。
今回は、前回作成したデータベースに登録したポケモンたちのデータと、Poke APIを用いてポケモンの画像を取得できるAPIを作成していきます。

今回の記事では最終的に、以下のようにポケモンの名前と画像が表示されるアプリが完成します。

![スクリーンショット 2024-12-28 224357.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3861322/28c9ff6f-975c-814a-edad-d53130301c2d.png)


# フォルダ構成
API 作成を TypeScript で行い、ブラウザへの表示を React を用いて実装します。まず、フォルダ構成を以下のように整理しましょう。

- pages/api/Pokemon
    - controllers : ビジネスロジックを管理します。
    - models : データベースとのやり取りや PokeAPI の呼び出しを行います。
    - dbs : データベース関連設定を格納します。
    - routes : API エンドポイントのルーティングを定義します。
    
また、pages/index.tsx はルートアクセス時のエントリーポイントとして使用します。

# モデルの作成
ポケモンの画像については、**PokeAPI**を用います。**PokeAPI**は簡単に言うとポケモンのデータを取得することができるデータベースのようなもので、RESTfulAPI形式でデータを取得することができます。詳しくは以下の記事を参考にしてください。

https://qiita.com/Syoitu/items/d4833cc0a0663fdfeed3

今回はデータベースに登録したポケモンの英語名 (english_name) からAPIを叩き、そのポケモンに対応する画像を取得します。

pages/api/Pokemon/models にcardModel.tsを作成します。
このファイルは前回作成したデータベースから、ポケモン名 (name)、英語名 (english_name)を取得し、さらにはその英語名からPokeAPIを叩き画像を取得します。

```typescript:cardModel.ts
import * as mysql from 'promise-mysql';
import axios from 'axios';

const BASE_URL: string = "https://pokeapi.co/api/v2/";

// データベース接続設定
const dbConfig = {
    host: 'localhost',
    user: 'root',
    password: '{MySQLで設定したパスワード}',
    database: 'poke_poke_mimic'
};

// データベースに登録したポケモンのname,english_nameを取得
export const getAllCardsFromDB = async (): Promise<any[]> => {
    let connection;
    try {
        connection = await mysql.createConnection(dbConfig);
        const rows = await connection.query('SELECT name, english_name FROM Cards');
        return rows;
    } catch (error) {
        console.error('Error fetching cards from database:', error);
        throw new Error('Failed to fetch cards from database');
    } finally {
        if (connection) {
            await connection.end();
        }
    }
};

/*
Poke APIからポケモンの画像URLを取得
*/
const getPokemonImageUrl = async (pokemonName: string): Promise<string> => {
    try {
        const response = await axios.get(`${BASE_URL}pokemon/${pokemonName.toLowerCase()}`);
        return response.data.sprites.front_default;
    } catch (error) {
        console.error(`Error fetching image for ${pokemonName}:`, error);
        throw new Error(`Failed to fetch image for ${pokemonName}`);
    }
};

/*
全てのポケモンの名前と画像URLを取得
*/
export const getAllCardNamesWithImagesFromDB = async (): Promise<any[]> => {
    try {
        const cards = await getAllCardsFromDB();
        const cardsWithImages = await Promise.all(cards.map(async (card) => {
            const imageUrl = await getPokemonImageUrl(card.english_name);
            return { name: card.name, imageUrl };
        }));
        return cardsWithImages;
    } catch (error) {
        console.error('Error fetching card names and images:', error);
        throw new Error('Failed to fetch card names and images');
    }
};
```

# コントローラーの作成
pages/api/Pokemon/controllersに cardController.tsを作成します。
このファイルは、カードに関連するAPIエンドポイントのロジックを定義するコントローラーファイルです。クライアントからのリクエストを処理し、データベースからデータを取得してレスポンスを返します。
```typescript:cardController.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { getAllCardNamesWithImagesFromDB } from '../model/cardModel';

/**
 * 全てのカードを取得
 * 
 * @param req - Next.jsのリクエストオブジェクト
 * @param res - Next.jsのレスポンスオブジェクト
 */
const getAllCardNamesWithImages = async (req: NextApiRequest, res: NextApiResponse) => {
    try {
        const cards = await getAllCardNamesWithImagesFromDB();
        res.status(200).json(cards);
    } catch (error) {
        console.error('Error fetching card names and images:', error);
        res.status(500).json({ error: 'Failed to fetch card names and images', details: error.message });
    }
};

export default getAllCardNamesWithImages;
```

# APIルートの作成
pages/api/Pokemon/routes/cardRoutes.ts に API エンドポイントを作成します。このルートは、GET リクエストを受け付け、コントローラーを呼び出します。
```typescript:cardRoutes.ts
import { NextApiRequest, NextApiResponse } from 'next';
import getAllCardNamesWithImages from '../controllers/cardController';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    if (req.method === 'GET') {
        await getAllCardNamesWithImages(req, res);
    } else {
        res.setHeader('Allow', ['GET']);
        res.status(405).end(`Method ${req.method} Not Allowed`);
    }
}
```


# フロントエンドの作成
取得したポケモンの名前と画像を表示する React コンポーネントを作成します。

- pages/CardList.tsx
ポケモンカードを表示するためのコンポーネントです。Axios を使って API からデータを取得し、画面にレンダリングします。

- pages/index.tsx
アプリケーションのエントリーポイントです。CardList コンポーネントを読み込みます。

pagesに新たにCardList.tsxというファイルを作成します。
```tsx:CardList.tsx
import React, { useEffect, useState } from 'react';
import axios from "axios";

interface Card {
    name: string;
    imageUrl: string;
}

const CardList: React.FC = () => {
    const [cards, setCards] = useState<Card[]>([]);
    const [loading, setLoading] = useState<boolean>(true);
    const [error, setError] = useState<string | null>(null);

    useEffect(() => {
        const fetchCards = async () => {
            try {
                const response = await axios.get<Card[]>('/api/Pokemon/routes/cardRoutes');
                setCards(response.data);
                setLoading(false);
            } catch (err) {
                setError('Failed to fetch cards');
                setLoading(false);
            }
        };

        fetchCards();
    }, []);

    if (loading) {
        return <div>Loading...</div>;
    }

    if (error) {
        return <div>{error}</div>;
    }

    return (
        <div>
            <h1>Pokemon Cards</h1>
            <div style={{ display: 'flex', flexWrap: 'wrap' }}>
                {cards.map((card) => (
                    <div key={card.name} style={{ margin: '10px', textAlign: 'center' }}>
                        <img src={card.imageUrl} alt={card.name} style={{ width: '100px', height: '100px' }} />
                        <p>{card.name}</p>
                    </div>
                ))}
            </div>
        </div>
    );
};

export default CardList;
```

index.tsxに以下のように記述します。
```tsx:index.tsx
import React from 'react';
import CardList from './CardList';

const App: React.FC = () => {
  return (
    <div className="App">
      <CardList />
    </div>
  );
};

export default App;
```

# 実行と確認
以下のコマンドで開発サーバーを起動し、アプリを確認します。

```bash
yarn dev
```
ブラウザで http://localhost:3000 を開くと、ポケモンの名前と画像が一覧表示されるはずです。（最初は Loading... が表示されますが、数分後にポケモンが表示されます）

# まとめ
ここまでで、ポケモンを表示するアプリが完成しました！
次回は、ログイン機能やユーザーごとのカード管理機能を作成してみます。お楽しみに！