VercelでQiitaのAPIを使ってみた


## 背景
自身のポートフォリオにQiitaの記事を掲載できないか調べていたところ、
Qiita APIを使用すれば実現できることを知り、実装してみた。

## 使用環境
- Vercel
- Next 14.2.13
- axios 1.9.0

今回はAPIの通信に、axiosを使用した。

## axiosのインストール
```bash
npm install axios@latest
```

## API通信部分の実装
Vercelでは、envをサーバーサイドで設定するため、クライアントサイドではenvを直接読み込めない。
そのため、サーバーサイドにAPIを作成し、リクエストする形をとる。

`app/api/qiita/route.ts`
```ts
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import axios from "axios";

export async function GET(req: NextRequest) {
  //process.env.(Vercelで設定した環境変数)とすることで、envを読み込める
  const token = process.env.QIITA_ACCESS_TOKEN;
  if (!token) {
    return NextResponse.json({ error: "No token set" }, { status: 500 });
  }

  try {
    const res = await axios.get(
      //?per_page=10をつければ10件まで取得できる
      'https://qiita.com/api/v2/authenticated_user/items?per_page=10',
      {
        headers: {
          Authorization: `Bearer ${token}` //Bearerをつけないとエラーになる
        }
      }
    );

    return NextResponse.json(res.data);
  } catch (e:any) {
    return NextResponse.json({ error: e.message }, { status: 500 });
  }
}

```
`NextResponse`は、以下のように書くことで、JSONの返却を行うヘルパークラスです。

```ts
import { NextResponse } from 'next/server';

return NextResponse.json(data);
```
ちなみに、以下のように書けば、ステータスを変更することも可能。
```ts
return NextResponse.json({ error: "Not found" }, { status: 404 });
```

開発環境では、`.env.local`に次のように記載した。

```env
QIITA_ACCESS_TOKEN= xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

## APIの取得結果を処理する

`app/(任意のフォルダ)/page.tsx`
```ts
useEffect(() => {
    fetch('/api/qiita')
      .then(res => res.json())
      .then(setData)
      .catch(console.error);
  }, []);
```

プロミスチェーンで記述。以下はそれぞれのコードの意味。

`useEffect(() => { ... }, [])`
初回のマウント時に`() => { ... },`を実行。

```ts
fetch('/api/qiita')  // /api/qiitaにHTTPリクエストを送る
  .then(res => res.json())　//そのレスポンスをjsonで受け取る
  .then(setData)　//その結果をsetDataに渡す
  .catch(console.error);　//エラーが出ればログで出力
```

setData関数はこんな感じ

```ts
const [articles, setArticles] = useState<QiitaItem[]>([]);
const setData = (articlesData: QiitaArticleProps[] | undefined): void => {
    //渡ってきたデータがなければ[]をセットする
    if (!articlesData) {
        setArticles([]);
    } else {
        const Data: QiitaItem[] = articlesData.map((data) => {
            //渡ってきたデータをオブジェクトとして返す
            return {
                comments_count: data.comments_count,
                created_at: data.created_at,
                id: data.id,
                likes_count: data.likes_count,
                private: data.private,
                reactions_count: data.reactions_count,
                stocks_count: data.stocks_count,
                tags: data.tags,
                title: data.title,
                updated_at: data.updated_at,
                url: data.url,
                page_views_count: data.page_views_count,
                user: {
                    id: data.user.id,
                    profile_image_url: data.user.profile_image_url
                }
            }
        });
        //オブジェクトをセットする
        setArticles(Data);
    }
}
```

あとは、オブジェクトをtsx内で以下のように読み込めば値を取得できる

```ts
return (
  <>
    {articles.map((article) => (
      <div key={article.id}>{article.title}</div>
    ))}
  </>
);

```


型定義のinterfaceは`app/types/qiita.ts`内に次のように記述

```ts
export interface QiitaArticleProps {
  rendered_body: string;
  body: string;
  coediting: boolean;
  comments_count: number;
  created_at: string;
  group: QiitaGroup;
  id: string;
  likes_count: number;
  private: boolean;
  reactions_count: number;
  stocks_count: number;
  tags: QiitaTag[];
  title: string;
  updated_at: string;
  url: string;
  user: QiitaUser;
  page_views_count: number;
  team_membership: QiitaTeamMembership;
  organization_url_name: string;
  slide: boolean;
}

export interface QiitaItem {
  comments_count: number;
  created_at: string;
  id: string;
  likes_count: number;
  page_views_count: number;
  private: boolean;
  tags: QiitaTag[];
  title: string;
  updated_at: string;
  url: string;
  user:{
    id: string;
    profile_image_url: string;
  }
}
```


今日は以上。ここまで。