Slide 1

Slide 1 text

aspidaで型安全にREST APIの バックエンドを呼び出したい ひがき 2024/07/20 PHP"オレ"カンファレンス神戸

Slide 2

Slide 2 text

自己紹介 ひがき 💻 PHP書いたり、TypeScript書いたり、Terraform書いたりしてます! 💻 バックエンド書くことが多いです 💻 最近関数型にすごく興味があります 💻 1年くらいPHP書いてなかったけど、これからPHP書く機会が増えそうなので参加しました! 🗼 2020年 - 2021年 まで神戸の板宿に住んでおりました!(神戸いい街) ♠️‍ 最近ポーカー始めました、オールイン!!! X @higaki_program

Slide 3

Slide 3 text

目次 はじめに aspidaとは 今回使用するAPIの説明 aspidaを使わずにAPIを呼び出した場合(私がやっていたやり方) aspidaを使ってAPIを呼び出した場合 まとめ

Slide 4

Slide 4 text

はじめに PHPの話はあんまりないです、、、 aspidaがいいよーって話です 他のライブラリの話はしません

Slide 5

Slide 5 text

aspidaとは クライアント側で REST API を呼び出す時に便利なOSS! リクエストのパラメータ、パス、レスポンスを型で担保してくれます(すごく嬉しい) axios / fetch / node-fetch の version があります 引用元:https://github.com/aspida/aspida/tree/main?tab=readme-ov-file#features

Slide 6

Slide 6 text

ちなみに axios: Node.jsやブラウザなど実行環境を意識せずに使用できるHTTPクライアントライブラリ fetch: ブラウザ標準のAPI node-fetch: ブラウザAPIのfetchをNode.jsで使用できるようにするためのライブラリ

Slide 7

Slide 7 text

こんなAPIがあったとします よくあるAPI GET: http://localhost/api/phpers PHPer一覧 GET: http://localhost/api/phpers/{id} PHPer詳細 POST: http://localhost/api/phpers PHPer登録 PUT: http://localhost/api/phpers/{id} PHPer更新 DELETE: http://localhost/api/phpers/{id} PHPer削除 GET: http://localhost/api/lunch-shops 神戸の美味しいランチのお店一覧 GET: http://localhost/api/lunch- shops/{id} 神戸の美味しいランチのお店の詳細

Slide 8

Slide 8 text

こんなAPIがあったとします

Slide 9

Slide 9 text

こんなAPIがあったとします

Slide 10

Slide 10 text

APIを呼び出す前の準備 を実行した後のdirectoryを想定しています $ yarn create react-app frontend-fetch . ├── node_modules │ ├── ... ├── README.md ├── package.json ├── public │ ├── ... ├── src │ ├── App.ts │ ├── index.ts │ ├── ... └── yarn.lock

Slide 11

Slide 11 text

注意点!! ここから先ツッコミどころ満載かと思いますが、新喜劇を見る気持ちでお願いします

Slide 12

Slide 12 text

aspidaを使わずに呼び出す(私のやっていたやり方) App.tsx APIを呼び出すフロントエンドのファイル ... export const App = () => { const createPHPer = async () => { try { // FIXME ここでAPI呼び出す } catch (error) { // エラー処理 } }; return (...); }

Slide 13

Slide 13 text

aspidaを使わずに呼び出す(私のやっていたやり方) App.tsx APIを呼び出すフロントエンドのファイル // FIXME ここでAPI呼び出す ... export const App = () => { const createPHPer = async () => { try { } catch (error) { // エラー処理 } }; return (...); }

Slide 14

Slide 14 text

aspidaを使わずに呼び出す(私のやっていたやり方) よし、書いていくぞ! // FIXME ここでAPI呼び出す

Slide 15

Slide 15 text

aspidaを使わずに呼び出す(私のやっていたやり方) axios 使って呼び出すぞ!! const response = await axios.post();

Slide 16

Slide 16 text

aspidaを使わずに呼び出す(私のやっていたやり方) いいぞいいぞ! const response = await axios.post(`http://localhost/api`);

Slide 17

Slide 17 text

aspidaを使わずに呼び出す(私のやっていたやり方) phper を作成するんだったな const response = await axios.post(`http://localhost/api/phper`);

Slide 18

Slide 18 text

aspidaを使わずに呼び出す(私のやっていたやり方) うわ、、、 any じゃん リクエストパラメータに何渡すんだっけ? 名前 と メールアドレス だし phperName と email やったはず、、、 const response = await axios.post(`http://localhost/api/phper`, { phperName: "神戸 太郎", email: "kobe-taro@phper.com", });

Slide 19

Slide 19 text

aspidaを使わずに呼び出す(私のやっていたやり方) 出力して中身を見るとする また any かよ const response = await axios.post(`http://localhost/api/phper`, { phperName: "神戸 太郎", email: "kobe-taro@phper.com", }); console.log(response.data.);

Slide 20

Slide 20 text

aspidaを使わずに呼び出す(私のやっていたやり方) でけた!!! ・・・・あれ?動かね、、、、 const response = await axios.post(`http://localhost/api/phper`, { phperName: "神戸 太郎", email: "kobe-taro@phper.com", }); console.log(response.data.phperName);

Slide 21

Slide 21 text

aspidaを使わずに呼び出す(私のやっていたやり方) うわ、色々ミスってるじゃん、、、 URL (誤)http://localhost/api/phper (正)http://localhost/api/phpers リクエストパラメータ (誤)phperName: "神戸 太郎", (正)familyName: "神戸", givenName: "太郎", レスポンス (誤)response.data.phperName (正)response.data.message const response = await axios.post(`http://localhost/api/phper`, { phperName: "神戸 太郎", email: "kobe-taro@phper.com", }); console.log(response.data.phperName);

Slide 22

Slide 22 text

aspidaを使わずに呼び出す(私のやっていたやり方) ここまでひどくはないと思いますが、 似たようなミスを経験した人いるんじゃないでしょうか

Slide 23

Slide 23 text

aspida を使用すると どうなるのか見てみましょう

Slide 24

Slide 24 text

aspidaを使って呼び出す(自力ファイル作成編) ... export const App = () => { const createPHPer = async () => { try { // FIXME ここでAPI呼び出す } catch (error) { // エラー処理 } }; return (...); } App.tsx

Slide 25

Slide 25 text

aspidaを使って呼び出す(自力ファイル作成編) aspidaクライアントを用いてAPIを呼び出します (作成方法は後述します) App.tsx import api from "./apiClient"; ... export const App = () => { const createPHPer = async () => {  try { // FIXME ここでAPI呼び出す } catch (error) { // エラー処理 } }; return (...); }

Slide 26

Slide 26 text

aspidaを使って呼び出す(自力ファイル作成編) aspidaクライアントを用いてAPIを呼び出します (作成方法は後述します) App.tsx import api from "./apiClient"; // FIXME ここでAPI呼び出す ... export const App = () => { const createPHPer = async () => {  try { } catch (error) { // エラー処理 } }; return (...); }

Slide 27

Slide 27 text

aspidaを使って呼び出す(自力ファイル作成編) 書いていく // FIXME ここでAPI呼び出す

Slide 28

Slide 28 text

aspidaを使って呼び出す(自力ファイル作成編) const response = await api.

Slide 29

Slide 29 text

aspidaを使って呼び出す(自力ファイル作成編) const response = await api.phpers.

Slide 30

Slide 30 text

aspidaを使って呼び出す(自力ファイル作成編) const response = await api.phpers.$post()

Slide 31

Slide 31 text

aspidaを使って呼び出す(自力ファイル作成編) const response = await api.phpers.$post({body: {}})

Slide 32

Slide 32 text

aspidaを使って呼び出す(自力ファイル作成編) const response = await api.phpers.$post({body: { email: "example@phper.com", family_name: "神戸", given_name: "太郎", }}); console.log(response.)

Slide 33

Slide 33 text

aspidaを使って呼び出す(自力ファイル作成編) おお!! なんと便利なことか

Slide 34

Slide 34 text

どうやったらaspida使えるようになるの??

Slide 35

Slide 35 text

aspidaを使って呼び出す(自力ファイル作成編) 手順 1. aspida の インストール 2. aspida 設定ファイル作成 3. APIエンドポイントの型定義ファイル作成 4. ビルド 5. aspidaクライアントを作成

Slide 36

Slide 36 text

aspidaを使って呼び出す(自力ファイル作成編) 手順 1. aspida の インストール 2. aspida 設定ファイル作成 3. APIエンドポイントの型定義ファイル作成 4. ビルド 5. aspidaクライアントを作成

Slide 37

Slide 37 text

aspidaを使って呼び出す(自力ファイル作成編) 1. aspida の インストール 今回は axios を使用してAPIを呼び出すことにします $ yarn add aspida @aspida/axios axios

Slide 38

Slide 38 text

aspidaを使って呼び出す(自力ファイル作成編) 手順 1. aspida の インストール 2. aspida 設定ファイル作成 3. APIエンドポイントの型定義ファイル作成 4. ビルド 5. aspidaクライアントを作成

Slide 39

Slide 39 text

aspidaを使って呼び出す(自力ファイル作成編) 2. aspida 設定ファイル作成 aspida.config.js を作成し、 input に (型定義のroot directory) を設定します 今回は src/api_type とします module.exports = { input: "src/api_type", }; │ ├── aspida.config.js . ├── ... │ ├── ... ├── src │ ├── App.ts │ ├── index.ts │ ├── ... └── yarn.lock

Slide 40

Slide 40 text

aspidaを使って呼び出す(自力ファイル作成編) 手順 1. aspida の インストール 2. aspida 設定ファイル作成 3. APIエンドポイントの型定義ファイル作成 4. ビルド 5. aspidaクライアントを作成

Slide 41

Slide 41 text

aspidaを使って呼び出す(自力ファイル作成編) 3. APIエンドポイントの型定義ファイル作成 型定義ファイルまでのディレクトリの階層構造がそのままAPI呼び出しのパスになります │ ├── api_type │ │ ├── lunch-shops │ │ │ ├── _id@number │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── phpers │ │ ├── _id@number │ │ │ └── index.ts │ │ └── index.ts . ├── ... │ ├── ... ├── src │ │ ├── @types │ │ │ └── index.ts │ ├── App.ts │ ├── index.ts │ ├── aspida.config.js

Slide 42

Slide 42 text

aspidaを使って呼び出す(自力ファイル作成編) 3. APIエンドポイントの型定義ファイル作成 GET, POST: http://localhost/api/phpers src/api_type/phpers/index.ts post: { status: 201 resBody: { message: string } reqBody: { family_name: string given_name: string email: string attends?: { location: '北海道' | '関西' | '東京' | '小田原' | '香川 year: number }[] | null | undefined } } import type * as Types from '../@types' export type Methods = { get: { status: 200 resBody: Types.PHPerListResource[] // 説明省略 } // → に記載 post: { ... } }

Slide 43

Slide 43 text

aspidaを使って呼び出す(自力ファイル作成編) 3. APIエンドポイントの型定義ファイル作成 GET: http://localhost/api/lunch-shops/{id} src/api_type/lunch-shops/_id@number/index.ts ※ パスパラメータは _ を先頭につける ※ @ で number 型 string 型を明示する( @ 以降をつけない場合は number|string ) import type * as Types from '../../@types' export type Methods = { get: { status: 200 resBody: Types.LunchShopShowResource // → に詳細記載 } } // src/api_type/@types/index.ts export type LunchShopShowResource = { id: string name: string zip_code: string prefecture: string city: string address: string floor?: string | null | undefined }

Slide 44

Slide 44 text

aspidaを使って呼び出す(自力ファイル作成編) 手順 1. aspida の インストール 2. aspida 設定ファイル作成 3. APIエンドポイントの型定義ファイル作成 4. ビルド 5. aspidaクライアントを作成

Slide 45

Slide 45 text

aspidaを使って呼び出す(自力ファイル作成編) 4. ビルド package.json に ビルド用のコマンドを追加し、実行します "scripts": { ... "api:build": "aspida" // 追加 } これで (型定義のroot directory)/$api.ts が作成されます $ yarn api:build

Slide 46

Slide 46 text

aspidaを使って呼び出す(自力ファイル作成編) 4. ビルド │ │ ├── $api.ts . ├── ... │ ├── ... ├── src │ ├── api_type │ │ ├── @types │ │ │ └── index.ts │ │ ├── lunch-shops │ │ │ ├── _id@number │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── phpers │ │ ├── _id@number │ │ │ └── index.ts │ │ └── index.ts │ ├── App.ts │ ├── index.ts │ ├── aspida.config.js │ ├── ... └── yarn.lock

Slide 47

Slide 47 text

aspidaを使って呼び出す(自力ファイル作成編) 手順 1. aspida の インストール 2. aspida 設定ファイル作成 3. APIエンドポイントの型定義ファイル作成 4. ビルド 5. aspidaクライアントを作成

Slide 48

Slide 48 text

aspidaを使って呼び出す(自力ファイル作成編) 5. aspidaクライアントを作成 ビルドで作成された $api.ts を用いて aspidaクライアントを作成します baseURL にはAPI呼び出し先のURLを記載します apiClient.ts import api from './api_type/$api'; import aspida from '@aspida/axios'; import axios from "axios"; const apiClient = api( aspida(axios, { baseURL: 'http://localhost/api', }) ); export default apiClient;

Slide 49

Slide 49 text

aspidaを使って呼び出す(自力ファイル作成編) 5. aspidaクライアントを作成 ビルドで作成された $api.ts を用いて aspidaクライアントを作成します baseURL にはAPI呼び出し先のURLを記載します apiClient.ts baseURL: 'http://localhost/api', import api from './api_type/$api'; import aspida from '@aspida/axios'; import axios from "axios"; const apiClient = api( aspida(axios, { }) ); export default apiClient;

Slide 50

Slide 50 text

aspidaを使って呼び出す(自力ファイル作成編) 5. aspidaクライアントを作成 │ ├── apiClient.ts . ├── ... │ ├── ... ├── src │ ├── api_type │ │ ├── $api.ts │ │ ├── @types │ │ │ └── index.ts │ │ ├── lunch-shops │ │ │ ├── _id@number │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── phpers │ │ ├── _id@number │ │ │ └── index.ts │ │ └── index.ts │ ├── App.ts │ ├── index.ts │ ├── aspida.config.js │ ├── ... └── yarn.lock

Slide 51

Slide 51 text

aspidaを使って呼び出す(自力ファイル作成編) いや、でも APIエンドポイントの型定義ファイル作成を自力で作るのすごく面倒だな、、、 これ自動で作ってほしいな、、、

Slide 52

Slide 52 text

OpenAPI から自動で型情報ファイルを作成できます!!

Slide 53

Slide 53 text

aspidaを使って呼び出す(OpenAPI経由編) openapi2aspida を使用することで、 OpenAPI から自動で型情報ファイルを作成できます!!

Slide 54

Slide 54 text

aspidaを使って呼び出す(OpenAPI経由編) 手順 1. aspida の設定変更 2. openapi2aspida による型情報ファイルの作成

Slide 55

Slide 55 text

aspidaを使って呼び出す(OpenAPI経由編) 手順 1. aspida の設定変更 2. openapi2aspida による型情報ファイルの作成

Slide 56

Slide 56 text

aspidaを使って呼び出す(OpenAPI経由編) 1. aspida の設定変更 aspida.config.js に openapi を追加し、OpenAPIのjson、yamlファイルを指定します ※ OpenAPIのjsonファイルを返却するURLの指定でも大丈夫です openapi: { inputFile: "./openapi.yaml" }, module.exports = { input: "src/api_type", baseURL: "http://localhost/api", }; openapi: { inputFile: "http://localhost/docs/api.json" }, module.exports = { input: "src/api_type", baseURL: "http://localhost/api", };

Slide 57

Slide 57 text

aspidaを使って呼び出す(OpenAPI経由編) 手順 1. aspida の設定変更 2. openapi2aspida による型情報ファイルの作成

Slide 58

Slide 58 text

aspidaを使って呼び出す(OpenAPI経由編) 2. openapi2aspida による型情報ファイルの作成 openapi2aspida を呼び出すことで型情報ファイルが自動で作成されます $ npx openapi2aspida ※ openapi2aspida のパッケージをインストールして呼び出しても良いです

Slide 59

Slide 59 text

自動で型情報作ってくれるのね!! 良いじゃん!!!

Slide 60

Slide 60 text

欲張り言いたい

Slide 61

Slide 61 text

エンドポイントが大量にあるシステムだと 可読性が悪くなったり、該当のAPI見つけるの大変かもーー

Slide 62

Slide 62 text

aspidaクライアントが呼び出すことができる エンドポイントを絞ることができます

Slide 63

Slide 63 text

aspidaを使って呼び出す(エンドポイント絞る編) 今回は、「神戸の美味しいランチのお店に関するエンドポイントのみ」 を呼び出すaspidaクライアントを作成します

Slide 64

Slide 64 text

aspidaを使って呼び出す(エンドポイント絞る編) 手順 1. aspida の設定変更 2. ビルド 3. エンドポイントを絞った aspida クライアントを作成 4. API呼び出し

Slide 65

Slide 65 text

aspidaを使って呼び出す(エンドポイント絞る編) 手順 1. aspida の設定変更 2. ビルド 3. エンドポイントを絞った aspida クライアントを作成 4. API呼び出し

Slide 66

Slide 66 text

aspidaを使って呼び出す(エンドポイント絞る編) 1. aspida の設定変更 aspida.config.js に outputEachDir を追加 outputEachDir: true, module.exports = { input: "src/api_type", baseURL: "http://localhost/api", openapi: { inputFile: "./openapi.yaml" }, };

Slide 67

Slide 67 text

aspidaを使って呼び出す(エンドポイント絞る編) 手順 1. aspida の設定変更 2. ビルド 3. エンドポイントを絞った aspida クライアントを作成 4. API呼び出し

Slide 68

Slide 68 text

aspidaを使って呼び出す(エンドポイント絞る編) 2. ビルド 設定変更後に、ビルドをすることでそれぞれのディレクトリに $api.ts が作成されます $ yarn api:build │ │ ├── lunch-shops │ │ │ ├── $api.ts │ │ └── phpers │ │ ├── $api.ts . ├── ... │ ├── ... ├── src │ ├── api_type │ │ ├── $api.ts │ │ ├── @types │ │ │ └── index.ts │ │ │ ├── _id@number │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── _id@number │ │ │ └── index.ts

Slide 69

Slide 69 text

aspidaを使って呼び出す(エンドポイント絞る編) 手順 1. aspida の設定変更 2. ビルド 3. エンドポイントを絞った aspida クライアントを作成 4. API呼び出し

Slide 70

Slide 70 text

aspidaを使って呼び出す(エンドポイント絞る編) 3. エンドポイントを絞った aspida クライアントを作成 呼び出したいエンドポイントのdirectoryにある $api.ts を用いて aspidaクライアントを作成します apiLunchShopClient.ts import api from './api_type/lunch-shops/$api'; import aspida from '@aspida/axios'; import axios from "axios"; const apiClient = api( aspida(axios, { baseURL: 'http://localhost/api', }) ); export default apiClient;

Slide 71

Slide 71 text

aspidaを使って呼び出す(エンドポイント絞る編) 3. エンドポイントを絞った aspida クライアントを作成 │ ├── apiLunchShopClient.ts . ├── ... │ ├── ... ├── src │ ├── api_type │ │ ├── ... │ ├── App.ts │ ├── index.ts │ ├── apiClient.ts │ ├── aspida.config.js │ ├── ... └── yarn.lock

Slide 72

Slide 72 text

aspidaを使って呼び出す(エンドポイント絞る編) 手順 1. aspida の設定変更 2. ビルド 3. エンドポイントを絞った aspida クライアントを作成 4. API呼び出し

Slide 73

Slide 73 text

aspidaを使って呼び出す(エンドポイント絞る編) 4. API呼び出し App.tsx import lunchShopApi from "./apiLunchShopClient"; ... export const App = () => { const createPHPer = async () => { ... }; const fetchLunchShop = async () => { try { // FIXME ここでAPI呼び出す } catch (error) { // エラー処理 } }; return (...); }

Slide 74

Slide 74 text

aspidaを使って呼び出す(エンドポイント絞る編) 4. API呼び出し App.tsx import lunchShopApi from "./apiLunchShopClient"; ... export const App = () => { const createPHPer = async () => { ... }; const fetchLunchShop = async () => { try { // FIXME ここでAPI呼び出す } catch (error) { // エラー処理 } }; return (...); }

Slide 75

Slide 75 text

aspidaを使って呼び出す(エンドポイント絞る編) 4. API呼び出し App.tsx // FIXME ここでAPI呼び出す import lunchShopApi from "./apiLunchShopClient"; ... export const App = () => { const createPHPer = async () => { ... }; const fetchLunchShop = async () => { try { } catch (error) { // エラー処理 } }; return (...); }

Slide 76

Slide 76 text

aspidaを使って呼び出す(エンドポイント絞る編) 4. API呼び出し 書いていく // FIXME ここでAPI呼び出す

Slide 77

Slide 77 text

aspidaを使って呼び出す(エンドポイント絞る編) 4. API呼び出し const response = await lunchShopApi.

Slide 78

Slide 78 text

aspidaを使って呼び出す(エンドポイント絞る編) 4. API呼び出し const response = await lunchShopApi._id.

Slide 79

Slide 79 text

aspidaを使って呼び出す(エンドポイント絞る編) 4. API呼び出し const response = await lunchShopApi._id.

Slide 80

Slide 80 text

aspidaを使って呼び出す(エンドポイント絞る編) 4. API呼び出し const response = await lunchShopApi._id.$get();

Slide 81

Slide 81 text

aspidaを使って呼び出す(エンドポイント絞る編) 4. API呼び出し const response = await lunchShopApi._id.$get(); console.log(response.)

Slide 82

Slide 82 text

エンドポイント絞れた!!

Slide 83

Slide 83 text

まとめ 👍 色々と型がついてくれるのは開発しやすい 👍 エンドポイント絞れるのも地味にありがたい エンドポイント多いと辿るのも大変だし 😭 aspidaクライアントに設定できる項目は適切ではないかも、、、 method 設定した場合、axiosだと無視されるがfetchだと無視されないなどがある 😭 OpenAPIを記述していない大きめなシステムだとaspida入れるの辛そう Laravelとかだと scramble で大雑把にOpenAPI導入して、細かい部分を後追いで修正していく感じにな りそう Scramble Scramble is OpenAPI (Swagger) documentation generator for Laravel. It generates API documentation for your project automatically without requiring you to manually write PHPDoc annotations. https://scramble.dedoc.co/

Slide 84

Slide 84 text

aspidaおすすめ!!!

Slide 85

Slide 85 text

おわり