脳死でaxiosを使うのを卒業して、fetch APIを使ってみた

Twitterでいつも参考にさせていただいている、[よわよわエンジニアさん](https://twitter.com/webengrchild)が「クライアントライブラリを脳死でAxiosを使う駆け出し勢があまりにも多過ぎる」件について発信をしていました。

見事に自分もその中の一人でしたので、これを機にFetchAPIに触れてみました。

参考↓
[https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch)

[https://blog.capilano-fw.com/?p=6646](https://blog.capilano-fw.com/?p=6646)

### 開発環境

Laravel8(APIモード) × Vue2（SPA）
現在axiosで記述しているコードをfetch APIで書き直す形式で進めていきます。
**何はともあれコードを書いていきます。**
## GETメソッド

### Controller(Laravel)

```php
public function index()
    {
        $users = USER::all();
        return response(['users' => $users, 'message' => 'Retrieved successfully'], 200);
    }
```

### axios（元のコード）

```php
axios
	.get("http://localhost:8000/api/user")
	.then((response) => {
	    this.users = response.data.users;
	    console.log(this.users);
	})
	.catch((response) => console.log(response));
```

### fetch APIで書き直し

```php
fetch('http://localhost:8000/api/user')
  .then(response => {
	  console.log(response);
      return response.json();
  })
  .then(data => {
      this.users = data.users;
      console.log(data.users);
  })
  .catch(error => {
      console.log(error);
  });
```

console.logの結果を確認

![スクリーンショット 2023-01-06 12.07.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981609/71657d31-6718-0ece-6463-5801a0df83bf.png)


`.then`を2回使用しているのは、1回目の`.then`の`response.json`がPromiseだから。

promiseチェーンの仕組みによって、結果がバンドラのチェーンに沿って渡され、次のPromiseに引き継がれます。Promiseの理解も怪しい…汗

参考；[https://ja.javascript.info/promise-chaining](https://ja.javascript.info/promise-chaining)

★ここで抑えておきたいポイント

- fetch()はPromiseのオブジェクトを返す（response.json()の戻り値もPromise）
- responseはJSONデータが格納されているresponseオブジェクト
- JSONデータの加工にはjson()メソッドを使う必要がある

## POSTメソッド

前提として、私が作業している開発環境では認証が必要です。認証について触れることで勉強になりました。

### Controller(Laravel)

```php
public function store(Request $request)
    {
        $data = $request->all();

        $validator = Validator::make($data, [
            'name' => 'required|max:255',
            'year' => 'required|max:255',
            'company_headquarters' => 'required|max:255',
            'what_company_does' => 'required'
        ]);

        if ($validator->fails()) {
            return response(['error' => $validator->errors(), 'Validation Error']);
        }

        $ceo = CEO::create($data);

        return response(['ceo' => new CEOResource($ceo), 'message' => 'Created successfully'], 200);
    }
```

### axios（元のコード）

```jsx
const data = {
  name: this.name,
  company_name: this.company_name,
  year: this.year,
  company_headquarters: this.company_headquarters,
  what_company_does: this.what_company_does
};

axios.post('api/ceo', data)
  .catch(function (error) {
    console.log(error);
  })
```

### fetch APIで書き直し

```jsx
const data = {
  name: this.name,
  company_name: this.company_name,
  year: this.year,
  company_headquarters: this.company_headquarters,
  what_company_does: this.what_company_does
};

const token = sessionStorage.getItem('access_token');

fetch('http://localhost:8000/api/ceo', {
    method: 'POST',
    headers: {
			'X-CSRF-TOKEN': '{{ csrf_token() }}',
      "Accept": "application/json",
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`
    },
    body: JSON.stringify(data)
  })
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.log(error);
  });
```

console.logの結果を確認
![スクリーンショット 2023-01-07 16.01.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981609/0b25cc5a-f067-6c1e-9904-2badcc4b539c.png)


重要なのが、`headers`に記述している`Aurthorization`です。

参考：[https://www.appsloveworld.com/reactjs/200/428/post-http-localhost3000-api-v1-stories-401-unauthorized](https://www.appsloveworld.com/reactjs/200/428/post-http-localhost3000-api-v1-stories-401-unauthorized)

axiosではデフォルトのヘッダー情報を`app.js`に設定をしています。

```php
// sessionStrageに保存しているAPI Tokenを取得
const api_token = window.sessionStorage.getItem('access_token');
// デフォルトでヘッダーに認証情報を付与
axios.defaults.headers.common['Authorization'] = "Bearer ".concat(api_token);
// 非同期でサーバーとやりとりするために使う
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
```

なので、axiosでは毎回`Authorization`の記述をする必要がありません。

axios同様、fetch APIでも同様の設定をしたいのですが未解決で時間がかかっているため、後日記事を編集する形で投稿したいと思います。

## Headerの中身を深掘り

### X-CSRF-TOKEN

そもそもCSRFとは？

webアプリの脆弱性を突いて攻撃する方法のこと。攻撃者のメリットは、攻撃者が自ら処理を実行することなく、ユーザーを介して処理を実行させることができる点。

例）攻撃者が攻撃用のWebページを作成、ユーザがアクセス→攻撃用のリクエストがアクセスしたユーザから攻撃対象に送信→攻撃されたwebアプリケーションが不正リクエストを処理、意図しない処理が実行される

参考：[https://qiita.com/mpyw/items/0595f07736cfa5b1f50c](https://qiita.com/mpyw/items/0595f07736cfa5b1f50c)

CSRFと密接な関係にあるOriginについて触れます。
Originとは
- スキーム：https
- ホスト：example.com
- ポート：443
これらをまとめてOriginと呼びます。

CSRF対策として重要なポイントはHTTPリクエストを送らせないことです。

リクエストの結果表示はブラウザのデフォルト機能で防御することが可能（同一オリジンポリシーの仕組み）なため、開発者はアプリケーションが悪意のあるユーザからのHTTPリクエスト自体を送信させない対策が必要です。

そこでCSRFトークンが登場します（やっと）。
CSRFトークンとは、正規のページからアクセスが行われていることを証明するための値です。
X-CSRF-TOKENをヘッダー情報に付与することで、悪意のあるユーザと正規のユーザをAPIが判定することができます。

ただ、他にもストレージにトークンを保持させることで判定する方法もあったりするので、そこの違いについても今後理解を深めていきたいです。

### Accept

クライアント側がどんなデータを処理できるかを表す。

今回の実装に関しては、Acceptがなくても実行可能なので、省略可能です。

### Content-Type

リクエスト時にメディアタイプ（MIMEタイプ）を指定する役割を果たしています。

Content-Typeを指定しない状態でPOSTすると、私の環境ではValidationエラーが返ってきます。APIがJSONのリクエストを期待しているのに、メディアタイプが指定されていないとメディアタイプを判定できないためです。

Content-TypeにもMIMEタイプという種類があって、リクエストの内容によってタイプが変わります(今回は`application/json`)。
基本的にはContent-Typeのヘッダは必須と考えて記述することが望ましいようです。

参考：[https://tech.stmn.co.jp/entry/2021/03/15/183722](https://tech.stmn.co.jp/entry/2021/03/15/183722)
参考：[https://blog.development-network.net/ung/プログラミング/content-type.html](https://blog.development-network.net/ung/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/content-type.html)

### Authorization
認証を受けるための証明書を保持します。
LaravelPassportで認証を強制しているので、今回はヘッダー情報にAuthorizationは必須となります。