PokeAPIを利用した二次開発

この記事はHowtelevision Advent Calendar 2022（https://qiita.com/advent-calendar/2022/howtelevision） 8日目の記事です。

![fb.jpeg](https://gamepedia.jp/ogp/gamepedia.jp/pokemon-sv/thumbnail/16178/8100fc5bee51488e4144d22c73c439b1)
## はじめに
皆さん、ポケモンランクマッチ楽しんでらしゃいますでしょうか:relaxed:？

「ポケモンSV」発売されて二週間を経て、やっと12月2日から記念すべき第一回のランクマッチが始まりました。
ランクマッチを存分楽しむために、私は普段幾つかのポケモン攻略サイトを利用してますが、こういったサービスもし自分で作る場合、どこから始めたら良いのか、

:point_up_tone1:軽く調べてみたら良いライブラリを見つけたので、紹介したいと思います。

## PokeAPI 

PokeAPIは簡単に言うとポケモンのデータを取得することができるデータベースのようなもの、 RESTfulAPIもしくはGraphQLの形式でデータを取得することができます。

https://pokeapi.co/

#### 使用例
特定のポケモンのデータを取得したい場合、下記のAPIにurlパラメーターとして、そのポケモンの`全国図鑑ID`、もしくは`英語名`を指定する必要があります。
```
https://pokeapi.co/api/v2/pokemon/{id or name}
```
例えば全国図鑑No.1である`フシギタネ`というポケモンのデータを取得したい場合、下記のようにリクエストを送ります。![91C986F6-0504-4A97-956B-617A3B27952F.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/40ba75fb-32e3-bf49-d20b-1e621e0ce15a.jpeg)

そのレスポンスからフシギタネの画像を取得したい場合は、
response -> sprites -> front_default の階層で取得できます。

![xx](https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png)

### 送信用のライブラリ
:point_up_tone1:さらに便利なのは、PokeAPI公式が用意してくれたリクエスト送信用のライブラリです、それを使えば自分でリクエストの送受信をパッケージ化する手間を省けます。

https://pokeapi.co/docs/v2#info

複数言語のバージョンがありますが、TypeScript対応のライブラリを`Next.js`プロジェクトで使用する方法を簡単に紹介します。

https://github.com/Gabb-c/pokenode-ts

*ディレクトリ抜粋*

```
...
- pages
- | - api
- | - | - info.ts
- | - | - list.ts
- | - pokemon
- | - | - [name].tsx
- | - | - index.tsx
```

作った機能は非常にシンプルです、
`一覧ページ`でポケモンの名前をクリックすれば、そのポケモンの`詳細ページ`に飛びます。

### 一覧ページ
![02F81962-0708-4737-B23B-CE342E14F4F9.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/0e3c8e4b-5fb7-b9e7-19ee-36cfa24c9c65.jpeg)

ライブラリのインストール

```
npm i pokenode-ts
# or
yarn add pokenode-ts # Recommended
```
一覧ページの中身は、`/api/pokemon/list`というAPIをただいて、返ってきたデータを表示します、
表示されたポケモン名をクリックすれば、そのポケモンの名前をパラメータとして使用され、詳細ページに飛びます。

```tsx:pages/pokemon/index.tsx
import { useEffect, useState, ReactElement } from 'react';
import Link from 'next/link';
import { AxiosResponse } from 'axios';
import { NamedAPIResourceList, NamedAPIResource } from 'pokenode-ts';

import request from '~/utils/axios';

const EffortPage: NextPageWithLayout = () => {
  const [resource, setResource] = useState<NamedAPIResource[]>();
  useEffect(() => {
    request
      .get(`/api/pokemon/list`, {})
      .then((res: AxiosResponse<NamedAPIResourceList>) => {
        setResource(res.data.results);
      });
  }, []);
  return (
    <div>
      <div className="overflow-x-auto">
        <table className="table w-full">
          <thead>
            <tr>
              <th></th>
              <th>ID</th>
              <th>NAME</th>
            </tr>
          </thead>
          <tbody>
            {resource?.map((item, index) => {
              return (
                <tr key={index}>
                  <th>{index}</th>
                  <td>
                    <Link href={`/pokemon/${item.name}`} legacyBehavior>
                      <a>{item.name}</a>
                    </Link>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
};

export default EffortPage;

```

`/api/pokemon/list`APIの中身は`pokenode-ts`ライブラリーから`PokemonClient`を使用し、`listPokemons`というメソッドで複数のポケモン情報を一覧ページに返します。

:point_up_tone1:その情報の中で、ページング、総数も含まれてますが、今回は使いません。

```typescript: pages/api/pokemon/list.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { PokemonClient } from 'pokenode-ts';

export default async function list(req: NextApiRequest, res: NextApiResponse) {
  const api = new PokemonClient();
  const listPokemon = await api.listPokemons();
  res.status(200).json({
    data: listPokemon,
  });
}
```



### 詳細ページ
![30F6091E-7955-4062-8051-411DE1E72F0F_1_105_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/422ff602-192e-0de5-50b6-a3b81a95172e.jpeg)

詳細ページは渡ってきたポケモンの名前を使って、 `/api/pokemon/info?name=${name}`というAPIをただき、返ってきたポケモンの詳細データを表示します。

```tsx: pages/pokemon/[name].tsx
import { AxiosResponse } from 'axios';
import { useRouter } from 'next/router';
import { Pokemon, PokemonSpecies } from 'pokenode-ts';
import { useState, useEffect, ReactElement } from 'react';

import request from '~/utils/axios';

interface Info {
  pokemonInfo: Pokemon;
  species: PokemonSpecies;
}

const Info: NextPageWithLayout = () => {
  const router = useRouter();
  const { name } = router.query;
  const [pokemon, setPokemon] = useState<Pokemon>();
  const [species, setSpecies] = useState<PokemonSpecies>();

  useEffect(() => {
    request
      .get(`/api/pokemon/info?name=${name}`)
      .then((res: AxiosResponse<Info>) => {
        setPokemon(res.data.pokemonInfo);
        setSpecies(res.data.species);
      });
  }, [name]);

  return (
    <div>
      <div className="card card-side bg-base-100 shadow-xl">
        <figure>
          <img src={pokemon?.sprites?.front_default as string} alt="Movie" />
        </figure>
        <div className="card-body">
          <h2 className="card-title">
            <span>全国No.{pokemon?.id}</span>
            {pokemon?.name}
          </h2>
          <div className="flex gap-1">
            {pokemon?.types.map((item, index) => {
              return (
                <div className="badge badge-success gap-2" key={index}>
                  {item.type.name}
                </div>
              );
            })}
          </div>
          <div>
            {pokemon?.stats.map((item, index) => {
              return (
                <div key={index}>
                  {item.stat.name}
                  {item.base_stat}
                  <input
                    type="range"
                    min="0"
                    max="200"
                    value={item.base_stat}
                    className="range range-info range-xs"
                  />
                </div>
              );
            })}
          </div>
          {species?.flavor_text_entries.map((item, index) => {
            console.log(item);
            if (item.language.name === 'ja-Hrkt') {
              return (
                <div key={index}>
                  Var.{item?.version?.name} : {item.flavor_text}
                </div>
              );
            }
          })}
        </div>
      </div>
    </div>
  );
};

export default Info;

```

`api/pokemon/info`APIの中身は`pokenode-ts`ライブラリーから`PokemonClient`を使用し、`getPokemonByName`、 `getPokemonSpeciesByName`二つのメソッドで該当ポケモンの情報を詳細ページに返します。

```typescript:pages/api/pokemon/info.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { PokemonClient } from 'pokenode-ts';

export default async function info(req: NextApiRequest, res: NextApiResponse) {
  const api = new PokemonClient();
  const { name } = req.query;
  const pokemonInfo = await api.getPokemonByName(name as string);
  const species = await api.getPokemonSpeciesByName(name as string);
  res.status(200).json({
    data: { pokemonInfo: pokemonInfo, species: species },
  });
}
```

## 最後に
pokeapiを使用すれば、確かに便利になりますが、
必要なポケモンデータが複数のメソッドに分散され、フロントの作業が多くなりがちという感想を受けました、
もしちゃんとしたアプリを作る場合、自分でDBを設計した上に、データ更新する目的でPokeApiを利用した方が良い気がします。
もし興味あれば、是非使ってみてください。