Qiita APIで自分の記事を取得・表示してみんなに見せびらかしたいんじゃ

## はじめに
最近少し学業の方も落ち着きつつあり(<-※卒論前の束の間の休息)，少しまとまった時間を取れたので，環境構築をするだけしてそんなに進められていなかったオリジナルのWebページ製作を進めていました．
その中でタイトルのようなことを考え，実際にページを製作したので，記事に残します．
今回は完全に趣味開発なので，いつもよりゆるい雰囲気でお送りします(｡∀゜)

## やりたいこと
「マイポータル的な扱いをしながら，自分の経歴や趣味なんかを共有できるような Webページの製作」というのが全体の目的ですが，今回はそのサイトの中で，

**Qiita APIにリクエストを送り，レスポンスを受け取った後に一覧で表示する**
機能を実現したいと考えました．自作のWebページ内で自分が書いた記事を一覧で出すことができれば，

- ポートフォリオとして使う場合に，わざわざQiitaのページに飛ばなくても，これまでの開発経験や技術スタックを確認してもらえる
- データベースに記事の内容等を保存しておく必要がなく，リソースを削減できる
- Qiitaのマイページ(下画像)では一発で確認できないコメント数や閲覧数を一覧で確認できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071957/063122e8-1f3e-51f9-1a1b-d48a053511cb.png)


## 開発環境
- react-dom@18.2.0
- react-router-dom@6.18.0
- material-ui/core@4.12.4
- axios@1.6.1

## 完成イメージ
完成イメージは以下です．
MaterialUIを活用して，赤四角内部のような各投稿1枚のCardコンポーネントを一列にな食べ，URL・いいね数・閲覧数・コメント数をひょうじしています．
個人的には，閲覧数について，記事の作成者以外の人はQiitaの投稿ページではその数を確認することができないので，「俺の記事こんなに見られてるんだぜ！」みたいに自慢できる点がポートフォリオとして使う上でもなかなか良いポイントだなと思っています．

余談ですが，カードの配色はQiitaで使われている色の`#55c500`にしました．気づいてくれる人いたら嬉しいな．

![IMG_82BFBB6B11AA-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071957/19f4d51e-2ba7-0ee1-0bcb-9e5274be4fbe.jpeg)

## 準備
QiitaAPIの利用には，[ユーザーの管理画面](https://qiita.com/settings/applications)から発行できるアクセストークンが必要になります．
ページから「個人用アクセストークン」を発行しましょう．
参考：[Qiita APIを触ってみる #初心者 - Qiita](https://qiita.com/miyuki_samitani/items/bfe89abb039a07e652e6)

## 実装
準備が完了したらいよいよ実装に移ります．

### 1. 型の定義
まずは，必要な型の定義を記載します．(仕様に合わせて選択し，記載してください)

<details><summary>各型の定義</summary>

```typescript:interfaces/index.ts
interface QiitaGroup {
  created_at: string;
  description: string;
  name: string;
  private: boolean;
  updated_at: string;
  url_name: string;
}

export interface QiitaTag {
  name: string;
  versions: string[];
}

interface QiitaUser {
  description: string;
  facebook_id: string;
  followees_count: number;
  followers_count: number;
  github_login_name: string;
  id: string;
  items_count: number;
  linkedin_id: string;
  location: string;
  name: string;
  organization: string;
  permanent_id: number;
  profile_image_url: string;
  team_only: boolean;
  twitter_screen_name: string;
  website_url: string;
}

interface QiitaTeamMembership {
  name: string;
}

export interface QiitaArticleProps { //all
  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;
}

export type QiitaProps = {
  config: any,
  data: QiitaArticleProps[];
  headers: string;
  request: string;
  status: number;
  statusText: string;
}

```
</details>

### 2. 投稿を取得する
次に，axiosを使ってQiitaAPIにHTTPリクエストを送信できるようにします．

```typescript:qiita.ts
import axios from "axios"
import { QiitaArticleProps} from "interfaces";

// 投稿の取得
export const getArticlesFromQiita = async (): Promise<QiitaArticleProps [] | undefined>=> {
    try {
        const res = await axios.get(
            'https://qiita.com/api/v2/authenticated_user/items',
            {
                headers: {
                    Authorization: 'Bearer [取得したアクセストークン]',
                },
            }
        );
        console.log(res);
        if (!res) return undefined;
        return res.data;
    }catch(e){
        console.log(e);
    }
};
```

### 3. 投稿表示用のコンポーネントの作成
表示ページから複数呼び出して書く投稿に応じた内容を表示するためのコンポーネントを実装します．

```typescript:components/utils/ArticleCard.tsx
import { useState } from "react";

import { extractDateFromISOString } from "lib/util";

import { Card, CardActions, CardContent, Avatar, Paper, Typography, IconButton, Grid, Icon, Link } from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
import { QiitaItem, QiitaTag } from "interfaces";
import { Link as routerLink } from "react-router-dom";

import LanguageTwoToneIcon from '@material-ui/icons/LanguageTwoTone';
import FavoriteIcon from '@material-ui/icons/Favorite';
import RemoveRedEyeIcon from '@material-ui/icons/RemoveRedEye';
import MessageIcon from '@material-ui/icons/Message';

const useStyles = makeStyles((theme) => ({
  card: {
    width: "80vm", 
    backgroundColor: "#56C600" // Qiitaの色
  },
  title: {
    flexGrow: 1,
    textDecoration: "none",
    color: "inherit"
  },
  linkBtn: {
    textTransform: "none",
    marginRight: "0"
  },
  paper : {
    height: "100%",
    margin: "20px 0px 0px 10px",
    padding: "5px"
  }
}));

export const ArticleCard = ({
  comments_count,
  created_at,
  id,
  likes_count,
  page_views_count,
  title,
  tags,
  url,
  updated_at,
  private: isPrivate
}: QiitaItem) => {
  const classes = useStyles();
  const [qiitaTags, setTags] = useState<QiitaTag[]>(tags);

  const createTagIcon = (tag: QiitaTag, id:number) => {
    return (
      <Paper key={id} elevation={2} className={classes.paper}>
        {tag.name}
      </Paper>
    );
  };

  return (
    <Grid item md={12}>
      <Card className={classes.card} id={id} elevation={6}>
        <CardContent className={classes.card}>
          <Typography component="div" variant="h5" style={{ marginTop: "0.5rem", fontWeight: "bold" } }>
            {title}
          </Typography>
          <Typography variant="body2">
              created : {extractDateFromISOString(created_at)}, last update : {extractDateFromISOString(updated_at)}
          </Typography>
          <Grid container spacing={2} >
            {tags.map((tag,index)=> createTagIcon(tag,index))}
          </Grid> 
        </CardContent>
        <CardActions>
          <>
            <IconButton
            component={routerLink} 
            to={url}
            className={classes.linkBtn}
            color="primary"
            >
            <LanguageTwoToneIcon/>    
            </IconButton>
          </>
            <Link href={url} color="inherit" style={{marginLeft:0}} underline="none">
            Go Page
          </Link>
          <>
            <Icon style={{marginLeft:20, color:"red"}}>
            <FavoriteIcon />
            </Icon>
            <p>{likes_count}</p>  
          </>
          <>
            <Icon style={{marginLeft:20}}>
              <RemoveRedEyeIcon/>
            </Icon>
            <p>{page_views_count}</p>   
          </>
          <>
            <Icon style={{marginLeft:20, color:"white"}}>
              <MessageIcon/>
            </Icon>
            <p>{comments_count}</p>  
          </>  
        </CardActions>
      </Card>
    </Grid>
  );
};
```

### 4. 表示ページの作成
最後に，これまでのコードを集約し，一覧ページをして表示するページを作成します．

```typescript:src/components/pages/Articles.tsx
import React, {useState} from 'react';
import { ArticleCard } from 'components/utils/ArticleCard';
import { getArticlesFromQiita } from 'lib/api/qiita';

import Box from "@material-ui/core/Box"
import Grid from "@material-ui/core/Grid"
import { QiitaArticleProps, QiitaItem} from 'interfaces';

const Articles = () => {
    const [articles, setArticles] = useState<QiitaItem[]>([])

    const setData = (articleData:  QiitaArticleProps [] | undefined): void => {
        if (!articleData) {
            setArticles([]);
            console.log("faild");
        } else {
            const Data: QiitaItem[] = articleData.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
                };
            });
            setArticles(Data);
        }
    }
    
    React.useEffect(() => {
        getArticlesFromQiita().then(setData);
    }, []); 
  
    return (
        <Box sx={{ flexGrow: 1 }}>
            <Grid container spacing={3}>
            {articles.map(a => (
                <ArticleCard
                comments_count={a.comments_count}
                created_at={a.created_at}
                id={a.id}    
                likes_count={a.likes_count}
                page_views_count={a.page_views_count}
                private={a.private}
                tags={a.tags}
                title={a.title}
                updated_at={a.updated_at}
                url={a.url}
                />
            ))}
            </Grid>
        </Box>   
    );
}

export default Articles;

```

## 動作確認
想定していた通り，各投稿に対応するタイトルやリンク，ステータスの表示を行うことができました．

![画面収録 2023-11-16 17.51.36.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1071957/516ecc29-fde0-b6dd-5c8f-349bec63dc87.gif)


## まとめ
今回は，QiitaAPIを利用してこれまで自分が投稿した記事の一覧を表示する機能の実装を行いました．私もいつかみんなが開発(趣味でも可)に使うようなAPIを作りたいです．
最後まで読んでいただきありがとうございました．

## 参考文献

- [Qiita API v2ドキュメント - Qiita Developer](https://qiita.com/api/v2/docs#%E3%81%84%E3%81%84%E3%81%AD)
- [Qiita API v2を使ってみる【JavaScript】 #JavaScript - Qiita](https://qiita.com/Jun-T/items/882be5c6d59f0e29c6c7)

（※いずれの記事も最終閲覧日は2023/11/16です．)