1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ReactでGitHub APIを叩いて自分のbio, statusを更新する

Last updated at Posted at 2021-12-16

はじめに

GitHubが提供するGitHub REST APIGitHub GraphQL APIを叩いて、
REST APIGraphQL APIの違いを確認しつつ、
自分のbio(自己紹介文)やステータスを更新したいと思います。

環境構築

あえてcreate-react-appは使わずに環境構築します(勉強になりそう)。

ターミナル
$ node -v
v16.13.0

$ npm -v
8.1.0

$ mkdir github-api
$ cd github-api

$ npm init -y
$ npm i react react-dom typescript
$ npm i -D webpack-cli webpack-dev-server html-webpack-plugin babel-loader @babel/preset-env @babel/preset-react @babel/preset-typescript @types/react @types/react-dom

$ gibo dump Node macOS >> .gitignore

$ npx tsc --init

// ローカルサーバ起動
$ npx webpack serve
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development",
  entry: './src/App.tsx',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  resolve: {
    extensions: [".js", ".ts", ".tsx"],
  },
  module: {
    rules: [
      {
        test: [/\.ts$/, /\.tsx$/],
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "src/index.html")
    })
  ],
  devServer: {
    static: {
      directory: path.join(__dirname, "dist"),
    },
  },
};
babel.config.js
module.exports = {
  presets: [
    '@babel/preset-env',
    '@babel/preset-react',
    '@babel/preset-typescript'
  ],
  targets: '> 1.5%, last 2 versions, not dead',
};
package.json(一部抜粋)
{
  一部省略

  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "typescript": "^4.5.2"
  },
  "devDependencies": {
    "@babel/preset-env": "^7.16.4",
    "@babel/preset-react": "^7.16.0",
    "@babel/preset-typescript": "^7.16.0",
    "@types/react": "^17.0.37",
    "@types/react-dom": "^17.0.11",
    "babel-loader": "^8.2.3",
    "html-webpack-plugin": "^5.5.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.6.0"
  }
}
tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

GitHub API 概要

2種類のAPIを使ってGitHubのbio, statusを更新してみます。

今回は、REST APIではPATCHリクエスト、GraphQL APIではPOSTリクエストを送信します。
そのため、認証を求められますが、GitHubで個人アクセストークンを作成するのは簡単です。
GitHubにログインして「Settings > Developper settings > Personal access tokens」と遷移します。
「Generate new token」ボタンからトークンを作成できます。
作成したトークンをHTTPリクエストのAuthorizationヘッダーに付加してリクエストを送信します。
今回はbio, statusを更新するだけなので、アクセス権のスコープは以下のようにuserのみにしました。
token_scopes.png

GitHub REST API を使った実装

GitHub Docsによると、REST APIのバージョンを指定したほうがいいそうです。

デフォルトでは、https://api.github.com へのすべてのリクエストが REST API の v3 バージョンを受け取ります。 Accept ヘッダを介してこのバージョンを明示的にリクエストすることをお勧めします。

なので、fetchを使ったリクエストは以下のようになりました。

Rest.tsx
import React, { useState, FormEvent } from "react";

export const Rest = () => {
  const token = '作成した個人アクセストークン';
  const [data, setData] = useState();
  const [bio, setBio] = useState<string>("");
  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    fetch('https://api.github.com/user', {
      method: "PATCH",
      body: JSON.stringify({ bio }),
      // 上記は、body: JSON.stringify({ 'bio': bio }), と等価
      headers: {
        Accept: "application/vnd.github.v3+json",
        Authorization: `bearer ${token}`,
      },
    })
      .then(res => res.json())
      .then(setData)
      .catch(console.error)
  }

  return (
    <>
      <h1>GitHub REST API で bioを更新</h1>
      <form onSubmit={handleSubmit}>
        <textarea onChange={e => setBio(e.target.value)} cols={50} rows={3} placeholder="your bio..." required />
        <button>更新する</button>
      </form>
      {data && <p>{`bioを「${data["bio"]}」に更新しました。`}</p>}
    </>
  )
}

ただのデモなのでアクセストークンを直書きしていますが、本来であれば、
ルートディレクトリに置いた.envファイルにトークンを書いて、dotenv-webpackなど、
webpackのコンパイル時に.envファイルに書いた環境変数をインポートするプラグインを使うのが良さそうです。
create-react-appを使ってReactのプロジェクトを作った場合は、環境変数を設定するために必要なdotenvというパッケージが予めインストールされているみたいです。)

App.tsx
import React from "react";
import ReactDOM from "react-dom";

import { Rest } from "./rest";

ReactDOM.render(
  <React.StrictMode>
    <Rest />
  </React.StrictMode>,
  document.getElementById('root')
);
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>GitHub API test</title>
</head>
<body>
    <div id="root" />
</body>
</html>

qiita_3.gif

GitHub GraphQL API を使った実装

続いてGraphQL APIを使ってGitHubのステータスを更新します。
ステータスのメッセージや絵文字を更新できるみたいです。
REST APIとの違いは、やはり単一のエンドポイントを持つというところでしょうか。

GraphQLのエンドポイント
REST APIは多数のエンドポイントを持ちますが、GraphQL APIは単一のエンドポイントを持ちます。
https://api.github.com/graphql
行う操作にかかわらず、エンドポイントは一定のままです。

GraphQLを利用するためのReact用ライブラリ等は使わず、
fetchを使って、リクエストのbodyquery, variablesを渡そうと思います。
以下を参考にしました。

Graphql.tsx
import React, { useState, FormEvent } from "react";

export const Graphql = () => {
  const token = '作成した個人アクセストークン';
  const [data, setData] = useState();
  const [message, setMessage] = useState<string>("");
  const [emoji, setEmoji] = useState<string>("");
  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    fetch('https://api.github.com/graphql', {
      method: "POST",
      body: JSON.stringify({
        query: `
          mutation updateStatusMutation($message: String!, $emoji: String!) {
            changeUserStatus(input: {message: $message, emoji: $emoji}) {
              status {
                message
                emoji
              }
            }
          }
        `,
        variables: {message, emoji}
      }),
      headers: {
        Authorization: `bearer ${token}`,
      },
    })
      .then(res => res.json())
      .then(setData)
      .catch(console.error)
  };

  return (
    <>
      <h1>GitHub GraphQL API で statusを更新</h1>
      <form onSubmit={handleSubmit}>
        <textarea onChange={e => setMessage(e.target.value)} cols={50} rows={2} placeholder="your status message..." required />
        <input type="text" onChange={e => setEmoji(e.target.value)} placeholder="your status emoji..." required />
        <button>更新する</button>
      </form>
      {data && <p>{`ステータスメッセージを「${data["data"]["changeUserStatus"]["status"]["message"]}」、ステータスの絵文字を「${data["data"]["changeUserStatus"]["status"]["emoji"]}」に更新しました。`}</p>}
    </>
  )
}

App.tsxindex.htmlは上と同じです。

qiita_graphql.gif

まとめ

最終的なディレクトリの構成はこんな感じになりました。

ターミナル
$ tree -I "node_modules|README.md|.gitignore" -L 2
.
├── babel.config.js
├── dist
│   ├── bundle.js
│   └── bundle.js.LICENSE.txt
├── package-lock.json
├── package.json
├── src
│   ├── App.tsx
│   ├── graphql.tsx
│   ├── index.html
│   └── rest.tsx
├── tsconfig.json
└── webpack.config.js

ちなみに、、

Api.tsx
<textarea onChange={e => setBio(e.target.value)} ...以下省略 />

このコードを見ると、textareaに入力されるたびにコンポーネントが再描画されると思うのですが、
Reactの本を読んでいたら、Reactの内部には重複した描画要求を調停する仕組みがあるため、
不要な再描画がされる心配はないと書いてありました。ただ、関数自体は入力のたびに呼び出されるから、
この描画関数内で重い処理を実行するのは避けるべきだそうです。

自分の備忘録ですが、誰かのためになれば幸いです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?