[初学者向け]Azure API Management + Functions（TypeScript）でAPIを公開してみよう

# はじめに

APIを使ったちょっとした検証することがあるのですが、毎度微妙にハマってる気がするので少しまとめてみました。

API GatewayとしてAPIManagementを使い、TypeScriptで作られたAzureFunctionsをバックエンドに置いた構成を作ってみます。
APIMはAzureのデプロイに40分くらいかかりますがお手元にnodejsが入っている環境があれば1時間もあれば十分に試せると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/21b1258e-3c97-104a-890e-e547f940ad01.png)

TypeScriptではありませんが以下もあわせてどうぞ。

- [[初学者向け]AzureFunctions(TimerTrigger)＋nodejsをはじめてみよう](https://qiita.com/uzresk/items/c3b312c5df16026bbcc2)
    - function core toolsについてはこちらの方が詳しく解説してあります。

# 前準備

- Azureサブスクリプション
- [nodejs](https://nodejs.org/ja/download/)&npmを使えるようにしておく
    - 私は12系を使いました。
- [Azure Functions Core Tools](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=linux%2Ccsharp%2Cbash#v2)
    - WSLを利用されている方はこちらの[手順](https://github.com/phr-poc-template/template/blob/master/docs/ja/DeployResources.md#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99)もどうぞ
    - このツールはデプロイだけではなくひな型の作成やローカル実行など手厚くサポートしてくれるfunctions開発するときになくてはならないツールです。v3をインストールしておきましょう。

# Azure functionsを作る

## TypeScriptのひな型を作る

ひな型を作るのはこれだけでOK

```bash
/products/azure-samples/api-management$ func init
Select a number for worker runtime:
1. dotnet
2. node
3. python
4. powershell
5. custom
Choose option: 2
node
Select a number for language:
1. javascript
2. typescript
Choose option: 2
typescript
Writing .funcignore
Writing package.json
Writing tsconfig.json
Writing .gitignore
Writing host.json
Writing local.settings.json
Writing /products/azure-samples/api-management/.vscode/extensions.json
```

## ビルドしてローカルで実行する

パッケージをインストール

```
npm install
```

HTTP Triggerをつくります。
見てもらったらわかる通り色々なトリガーのひな型が一発で作れます。超便利。

```
/products/azure-samples/api-management$ func new hello
Select a number for template:
1. Azure Blob Storage trigger
2. Azure Cosmos DB trigger
3. Durable Functions activity
4. Durable Functions HTTP starter
5. Durable Functions orchestrator
6. Azure Event Grid trigger
7. Azure Event Hub trigger
8. HTTP trigger
9. IoT Hub (Event Hub)
10. Azure Queue Storage trigger
11. RabbitMQ trigger
12. SendGrid
13. Azure Service Bus Queue trigger
14. Azure Service Bus Topic trigger
15. SignalR negotiate HTTP trigger
16. Timer trigger
Choose option: 8
HTTP trigger
Function name: [HttpTrigger] hello
Writing /products/azure-samples/api-management/hello/index.ts
Writing /products/azure-samples/api-management/hello/function.json
The function "hello" was created successfully from the "HTTP trigger" template.
```

これでindex.tsはできてる。ここまで3分。

```typescript
import { AzureFunction, Context, HttpRequest } from "@azure/functions"

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    context.log('HTTP trigger function processed a request.');
    const name = (req.query.name || (req.body && req.body.name));
    const responseMessage = name
        ? "Hello, " + name + ". This HTTP triggered function executed successfully."
        : "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.";

    context.res = {
        // status: 200, /* Defaults to 200 */
        body: responseMessage
    };

};

export default httpTrigger;
```

ビルドする（distにソースが配置される）

```
npm run build
```


ローカルで動かしてみましょう。

```
/products/azure-samples/api-management$ func host start
Azure Functions Core Tools (3.0.2881 Commit hash: fa9641ad925588192888bc977ff51d8a63536267)
Function Runtime Version: 3.0.14287.0
Hosting environment: Production
Content root path: /mnt/c/Users/uzres/products/azure-samples/api-management
Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.

Functions:

        hello: [GET,POST] http://localhost:7071/api/hello

For detailed output, run func with --verbose flag.
Worker process started and initialized.
```

postmanで`http://localhost:7071/api/hello`にアクセスすると確認できることを確認しておきましょう。


## デプロイする

Azure FunctionsをAzure上に用意する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/3cdc4107-556c-07d4-66f1-2f93fc3496ea.png)

デプロイする

```
func azure functionapp publish [function名]
```

Invoke urlというところにfunctionsのURLが書かれるのでpostmanで実行して動くことを確認しておきましょう。これは親切ですね。

# API Management経由で動かしてみよう

## functionsをimportしてテスト実行する

API Managementを作るところは簡単すぎるので割愛。ただし40分くらいかかる・・・。

先ほど作ったfunctionsをimportします。ガイドに従って設定していけば大丈夫。迷うところはないです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/1b5122b0-efb9-bc7b-6702-dd83b7f3d238.png)

こんな感じでGETとPOSTのhelloが追加されていると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/5e48525c-f349-f612-c709-f3dcbe8c2e89.png)

テストを動かしてみましょう。

[Test]タブを開いて、パラメータを設定して実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/673ff200-2e57-0d35-1a28-01034bf5eec4.png)

成功したら右上の「Generate definition」をクリックして定義を生成しておきます。

## OpenAPI定義のダウンロード（おまけ）

APIを選択し、export -> OpenAPIv3 を選択するとダウンロードできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/4e02a236-b19e-99b2-7c2e-6f4509f69980.png)

先ほどの手順で「Generate definition」しておくとより詳細な定義が出力されます。

Swagger Editorにもそのままインポートして確認することもできます。（Descriptionは書いてないのでエラーになってますけどね）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/c4f440d2-bc64-6950-b036-df2c05494635.png)

## 外部からAPIを叩く

外部からAPIを叩くにはサブスクリプションキーをHTTP Header(Ocp-Apim-Subscription-Key)に設定することでアクセス可能です。
サブスクリプションを発行するにはそれぞれの用語を整理しておいた方がよさそうです。

APIと製品、ユーザ（サブスクライバ）、サブスクリプションの関係は以下のようになります。
ユーザの上にはグループという概念がありますが、サブスクリプションを発行してAPIに接続するだけであれば必要ありません。今回は製品を作成してそこにサブスクリプションを紐づけてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/efd89b74-1dcc-faa9-7e23-cd5bc733b97d.png)

まずは製品を作って、それにAPIを紐づけていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/f294e7d0-3afd-3f6c-b0f5-dbf5ff2aa914.png)

サブスクリプションを発行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/cd7506fd-80e2-d95c-c12c-764fb406e39e.png)

サブスクリプションに紐づく主キー、２次キーはいつでも見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/b82f08c4-2b21-f417-4b6c-a850ca88171e.png)

キーがコピー出来たらそれを使ってpostmanでアクセスしてみましょう。

- HTTPHeaderはOcp-Apim-Subscription-Keyです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/bfb214cf-78b5-45ea-b851-0025394626c5.png)

## APIに制限を付ける

詳しくは[ドキュメント](https://docs.microsoft.com/ja-jp/azure/api-management/api-management-access-restriction-policies)を参照していただければよいと思いますが色々とできます。

- ヘッダを確認する
- サブスクリプション別に呼び出しレートを設定する
- キー別に呼び出しレートを設定する
- IPを制限する
- 使用量のクォータをサブスクリプション別に設定する
- 使用量のクォータをキー別に設定する
- JWTを検証する

割とお手軽そうな[IP制限](https://docs.microsoft.com/ja-jp/azure/api-management/api-management-access-restriction-policies#restrict-caller-ips)だけ試してみましょう。

inboundの通信元を自宅のみにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/0569c6a4-a0b1-fa47-783f-0ed22533c5c1.png)

自宅でアクセス後、会社からだと・・・・403が返って来ました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57686/99c74f90-b0bf-7b14-ea62-40146418d264.png)


# 参考

- [Azure API Management 俺的マニュアル](https://www.slideshare.net/uesaka/azure-api-management-124244263)
    - 神のような資料でした。ありがとうございました。