Vue 3 + TypeScript + axiosでAPI接続

## 概要
Vue 3 + TypeScript + axiosでAPIへ接続する方法の基礎となる部分をまとめた。
(axiosとはブラウザやnode.js上で動くPromiseベースのHTTPクライアント)

Vue初心者がハマりがちな[CORSのエラー](#corsのエラー)についても後述する。

## 環境
- Vue 3.0.0
- TypeScript 4.1.6
- axios 0.21.1

## Install vue/cli

```console
$ yarn global add @vue/cli
```

## Create App
Vueのアプリケーションを作成する。

```console
$ vue create sample-app
```

TypeScriptとRouterを有効にする。

```console
Vue CLI v4.5.13
? Please pick a preset: Manually select features
? Check the features needed for your project:
 ◉ Choose Vue version
 ◉ Babel
 ◉ TypeScript
 ◯ Progressive Web App (PWA) Support
❯◉ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing
```

Vueのversionは3.xを選ぶこと。それ以外はお好みで。

```console
Vue CLI v4.5.13
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, Router, Linter
? Choose a version of Vue.js that you want to start the project with 3.x
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: (Use arrow keys)
❯ ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier
  TSLint (deprecated)
```

## axiosをインストール

```console
$ yarn add axios
```

## axiosの共通設定
src/の下にhttp-common.tsという名前のファイルを作成し、axiosを使う際のheadersなどの共通設定を定義する。baseURLにはAPIのURIを定義している。

```js:src/http-common.ts
import axios, { AxiosInstance } from "axios";

const apiClient: AxiosInstance = axios.create({
  // APIのURI
  baseURL: "http://localhost:3000",
  // リクエストヘッダ
  headers: {
    "Content-type": "application/json",
  },
});

export default apiClient;
```

## APIへのエンドポイント
services/SampleApiService.tsというファイルを作成し、APIの各エンドポイントの定義をする。先程作成したhttp-common.tsをimportして使う。

```js:services/SampleApiService.ts
import http from "@/http-common";

class SampleApiService {
  getAll(): Promise<any> {
    return http.get("/api/books");
  }

  get(id: any): Promise<any> {
    return http.get(`/api/books/${id}`);
  }

  create(data: any): Promise<any> {
    return http.post("/api/books", data);
  }

  update(id: any, data: any): Promise<any> {
    return http.put(`/api/books/${id}`, data);
  }

  delete(id: any): Promise<any> {
    return http.delete(`/api/books/${id}`);
  }

  deleteAll(): Promise<any> {
    return http.delete(`/api/books`);
  }

  findByDescription(title: string): Promise<any> {
    return http.get(`/api/books?title=${title}`);
  }
}

export default new SampleApiService();
```

## componenetから利用する
各componentからこんな感じで呼び出して利用できるようになる。

```js:components/sample.vue
import SampleApiService from "@/services/SampleApiService";

SampleApiService.getAll();
SampleApiService.get(1);
SampleApiService.deleteAll();
```

## CORSのエラー
Vue.jsのサーバーと、APIのサーバーのOriginが異なる場合、APIアクセス時に以下のようなエラーが出ることがある。

> Access to XMLHttpRequest at 'http://localhost:3000/api' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

例えば、Vue.jsのサーバーが http://localhost:8080 、APIサーバーが http://localhost:3000 のようにそれぞれのOriginが異なる場合、単純リクエスト（GET, HEAD, POST）以外のリクエスト時にこのエラーが起きる場合がある。

単純リクエスト以外、つまりDELETE、PUTなどのリクエスト時には、リクエスト送信前にpreflightリクエストというものが飛び、そのHTTPメソッドが相手先サーバーで許可されているかどうかの確認を行う。相手先サーバーで許可されていない場合にこのエラーが発生する。

解決策は以下の２つがあるが、2のproxyを使った方法が簡単であり、そちらを取る場合が多いようなので、ここでもproxyの設定方法を説明する。

1. 相手先サーバー（今回の場合はAPIサーバー）でOriginの許可設定をする
2. Vue.jsサーバーでproxyを利用する

- **参考**
    - [CORSまとめ](https://qiita.com/tomoyukilabs/items/81698edd5812ff6acb34)
    - [オリジン間リソース共有 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)
    - [Vue.jsとAPIサーバとのaxiosでCORSに引っかかった時のProxyを使った回避方法](https://qiita.com/Ryoma0413/items/c41d10d2e6e2a420c3cf)

## proxyの設定
root階層、package.jsonのある階層にvue.config.jsを作成し、proxyの設定をする。

以下の設定をすることで、devServer（`vue-cli-service serve`で動くサーバー）上で、http://localhost:8080 へのアクセスで且つ、URIに/apiを含むリクエストが発行された場合、宛先を http://localhost:3000 に書き換えてアクセスするようになる。

つまり、Vue.jsサーバーがhttp://localhost:8080 、APIサーバーがhttp://localhost:3000の場合、 http://localhost:8080/api/** にアクセスすると、 http://localhost:3000/api/** にリクエストが書き換えられアクセスされる。

これにより、ブラウザ上では http://localhost:8080/api/** へアクセスしていることになるので、CORSのエラーを回避することができる。

```js:vue.config.js
module.exports = {
  devServer: {
    proxy: {
      "^/api*": {
        target: "http://localhost:3000",
        pathRewrite: { "^/api": "" },
        changeOrigin: true,
        logLevel: "debug",
      }
    }
  }
};
```

## baseURLを削除
http-common.tsのbaseURLがあるとproxyが効かなくなるので削除する。

```diff:src/http-common.ts
import axios, { AxiosInstance } from "axios";

const apiClient: AxiosInstance = axios.create({
- // APIのURI
- baseURL: "http://localhost:3000",
  // リクエストヘッダ
  headers: {
    "Content-type": "application/json",
  },
});

export default apiClient;
```

これでCORSのエラーを回避できる。

## 参考
- https://www.bezkoder.com/vue-3-typescript-axios/