はじめに
GitHubが提供するGitHub REST API
とGitHub GraphQL API
を叩いて、
REST API
とGraphQL 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
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"),
},
},
};
module.exports = {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
],
targets: '> 1.5%, last 2 versions, not dead',
};
{
一部省略
"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"
}
}
{
"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
のみにしました。
GitHub REST API を使った実装
GitHub Docs
によると、REST APIのバージョンを指定したほうがいいそうです。
デフォルトでは、https://api.github.com へのすべてのリクエストが REST API の v3 バージョンを受け取ります。 Accept ヘッダを介してこのバージョンを明示的にリクエストすることをお勧めします。
なので、fetchを使ったリクエストは以下のようになりました。
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
というパッケージが予めインストールされているみたいです。)
import React from "react";
import ReactDOM from "react-dom";
import { Rest } from "./rest";
ReactDOM.render(
<React.StrictMode>
<Rest />
</React.StrictMode>,
document.getElementById('root')
);
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>GitHub API test</title>
</head>
<body>
<div id="root" />
</body>
</html>
GitHub GraphQL API を使った実装
続いてGraphQL API
を使ってGitHubのステータスを更新します。
ステータスのメッセージや絵文字を更新できるみたいです。
REST API
との違いは、やはり単一のエンドポイントを持つというところでしょうか。
GraphQLのエンドポイント
REST APIは多数のエンドポイントを持ちますが、GraphQL APIは単一のエンドポイントを持ちます。
https://api.github.com/graphql
行う操作にかかわらず、エンドポイントは一定のままです。
GraphQLを利用するためのReact用ライブラリ等は使わず、
fetchを使って、リクエストのbody
にquery
, variables
を渡そうと思います。
以下を参考にしました。
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.tsx
とindex.html
は上と同じです。
まとめ
最終的なディレクトリの構成はこんな感じになりました。
$ 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
ちなみに、、
<textarea onChange={e => setBio(e.target.value)} ...以下省略 />
このコードを見ると、textarea
に入力されるたびにコンポーネントが再描画されると思うのですが、
Reactの本を読んでいたら、Reactの内部には重複した描画要求を調停する仕組みがあるため、
不要な再描画がされる心配はないと書いてありました。ただ、関数自体は入力のたびに呼び出されるから、
この描画関数内で重い処理を実行するのは避けるべきだそうです。
自分の備忘録ですが、誰かのためになれば幸いです。