Twilio CLI（サーバーレス開発編）

:::note alert
2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
:::

# はじめに

みなさん、こんにちは。
KDDIウェブコミュニケーションズのTwilio事業部エバンジェリストの高橋です。

前々回の「[Twilio CLI（セットアップ編）](https://qiita.com/mobilebiz/items/456ce8b455f6aa84cc1e)」、前回の「[Twilio CLI（API操作編）](https://qiita.com/mobilebiz/private/0c687a1cd66772885d6e)」に続いて、最後はTwilio CLIでサーバーレスの開発を行うための方法をご紹介します。本題に入る前に、まずはTwilioのサーバーレスについて簡単に説明します。
Twilioでは、電話の操作をするために**TwiML**というマークアップ言語を使います。以前は、このTwiMLを生成したり、ホストするために外部のサーバーを連携させる必要があったのですが、現在は外部のサーバーを用意しなくても、Twilio内部でTwiMLをホストしたり、生成することが可能になっています。
これはたとえば以下のような機能になります。

#### [TwiML Bins](https://jp.twilio.com/docs/runtime/tutorials/twiml-bins)

Twilioの管理コンソールを使って、Twilioの内部に直接TwiMLをホストする機能です。管理コンソール上で直接TwiMLを記述することができるので、スタティックなTwiMLを返す場合に便利です。

#### [Twilio Assets](https://jp.twilio.com/docs/runtime/assets)

Twilioが持つストレージサービスです。mp3ファイルのようなTwiMLから呼び出しが行われるファイル以外にも、HTMLファイルやJavaScriptファイル、CSSファイルなどもホストすることができ、それぞれにパブリックなURLが払い出されるため、Webサーバーとしてコンテンツを表示するなども可能になります。
パブリックなURLを払い出す方法だけでなく、外部からはアクセスできずFunctionsからのみアクセス可能なプライベートな保存も可能です。

#### [Twilio Functions](https://jp.twilio.com/docs/runtime/functions)

Twilio Functionsは、Twilioが提供するFaaS（Functions as a Service）です。Node.jsという言語のコードをTwilio上に直接記述することで、そのコードを実行する役割を持ちます。エンジニアはサーバーを意識することなく、コードだけに集中することができ、かつ月に10,000回までの実行が無料です（10,001回目からも非常に安価です）。
動的にTwiMLを生成することはもちろん、外部のWebサービスと連携したり、多岐にわたった処理を実行することができます。

このようにTwilioには便利な機能が用意されているのですが、従来の欠点は、これらの機能が管理コンソールからしか扱えないということでした。たとえば、Assetsにファイルをアップロードするのも管理コンソールから手動で行う必要がありますし、Functionsのコードを修正するのもすべて管理コンソール経由でした。
開発者の多くは、ローカルPC上でコーディングをしテストをしてからサーバーにデプロイする手法を好みます。これを実現するのが、今回ご紹介する**Twilio CLI Serverless Plugin**になります。

## Twilio CLI Serverless Pluginとは

Twilio CLI Serverless Pluginは、その名の通りTwilio CLIのプラグインになります。Twilio CLIにはプラグイン機能があり、Twilioが提供するいくつかのプラグインだけでなく、自分自身でプラグインを作ることもできます。
Serverless Pluginを使うことで、以下のことが可能になります。

- ローカルPC上にTwilioの開発環境を構築することができます。
- 開発したFunctionsをローカルで実行したり、デバッグすることができます。
- Assets機能をローカルディスク上で実現し、Functionsから読み込むこともできます。
- ローカル上で作った実行環境をサーバーにデプロイすることができます。

# Serverless Pluginのインストール

Twilio CLIにServerless Pluginをインストールするには、以下のコマンドを利用します。

```sh
twilio plugins:install @twilio-labs/plugin-serverless
```

インストールが終わると、serverlessコマンドが有効になります。

<img width="903" alt="スクリーンショット 2019-12-01 14.54.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86046/5ff5f1cd-3bf4-5ac5-544e-f193b5445b7a.png">

# サンプルプロジェクトを作ってみる

ではここから、Serverless Pluginを使ってローカル開発環境を構築してみましょう。

サンプルプロジェクトを作成したフォルダに移動し、以下のコマンドを入力します。

```
twilio serverless:init first-project
```
以下のような画面が表示され、自動的にサンプルのプロジェクトが出来上がります。
![スクリーンショット 2019-12-12 11.27.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86046/57f445ff-3c36-1a75-843e-d2e43ad6bc16.png)

プロジェクトが完成したら、そのプロジェクトに移動します。

```sh
cd first-project
```

プロジェクトは以下のようなディレクトリ構造を持っています。

```sh
./
├── assets
│   ├── index.html
│   ├── message.private.js
│   └── style.css
├── functions
│   ├── hello-world.js
│   ├── private-message.js
│   └── sms
│       └── reply.protected.js
├── node_modules
├── package-lock.json
└── package.json
```

### assetsフォルダ

こちらには、３つのファイルがあります。この内、`message.private.js`のように、拡張子の直前に`private.`が含まれる場合、このファイルはプライベート（外部からアクセス不可）な状態でデプロイされます。デプロイ時に`private.`は除かれます。

### functionsフォルダ

こちらには２つのファイルと１つのフォルダ（`sms`）が配置され、さらにそのフォルダの中にも1つファイルが生成されているのがわかります。`sms`フォルダの中にある、`reply.protected.js`は、`protected.`が含まれているため、このFunctionに関しては、外部からアクセス時に認証が必要です（同じアカウント内から読み込む場合は認証は不要です）。


# ローカルで実行

以下のコマンドを入力すると、ローカル環境で簡易的なWebサーバーが起動し、その上でFunctionsが動作していることが確認できます。

```sh
npm start
```

起動している状態でブラウザを開き、`http://localhost:3000/hello-world`を入力してみると、以下のようなページが表示されるはずです。
![スクリーンショット 2019-12-12 14.25.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86046/279ab765-1229-1c7f-b506-e05fbda41e64.png)

また同様に、Assetsに関してもローカルでアクセスが可能になります。同じくブラウザで`http://localhost:3000/index.html`と入力してみると、以下のページが表示されます。
![スクリーンショット 2019-12-12 14.27.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86046/aa074ee2-f26f-1b2c-98f5-9c155dc8e4ff.png)

終了させるには`ctrl-c`を入力します。

もしローカルで実行中のFunctionsやAssetsを外部からもアクセスさせたい場合は、[ngrok](https://ngrok.com/)を利用します。
先程のコマンドを実行した後、自分でngrokを起動してもよいですが、以下のコマンドを利用するとngrokの起動までが自動で行えるので便利です。

```
twilio serverless:start --ngrok=""
```
ngrokのサブドメインをお持ちの方であれば、最後の`""`のところに、サブドメインを指定することもできます。

# デプロイ

ローカルでの開発が完了したら、いよいよサーバーにデプロイしましょう。
デプロイ作業は簡単です。以下のコマンドを入力するだけで、ローカル環境がデプロイされます。

```
twilio serverless:deploy
```

以下のような画面が表示され、しばらくするとデプロイが完了します。

![スクリーンショット 2019-12-12 17.11.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86046/42f8e157-0715-e587-f24e-ca9533c372b5.png)

ユニークなドメイン（上記の例では`first-product-4106-dev.twil.io`）が設定された状態でデプロイが完了していることがわかります。
試しに、Assetsにある`index.html`を開いて、先程と同じ画面が表示されることを確認しましょう。

# 削除

デプロイしたサービスを削除するには、以下の手順を実行します。

現在デプロイされているサービスの一覧を取得する。

```sh
twilio api:serverless:v1:services:list
```

削除したいサービスのSIDを調べたら、以下のコマンドでサーバー上から削除します。

```
twilio api:serverless:v1:services:remove --sid [削除したいサービスのSID]
```

最後に、ローカルフォルダ内にある`.twilio-functions`というファイルを削除します。
<font color='red'>このファイルが残っていると、次回のデプロイ時にエラーが出てデプロイに失敗します。</font>
削除後に再度デプロイしたときは、生成されるAssetsやFunctionsのURLが変更される点にも注意してください。

# 補足

## 環境変数

管理コンソールでFunctionsを使ったことがある方はご存知かと思いますが、Functionsでは環境変数が使えます。
Serverless Pluginを使う場合も、同様に環境変数が使えます。
プロジェクトディレクトリに、`.env`というファイルがありますので、そこに環境変数を指定しておくとデプロイ時にサーバー側に反映されます。デフォルトでは、`ACCOUNT_SID`と`AUTH_TOKEN`という変数が設定されています。

## 外部モジュール

Functionsから外部モジュールを使う場合は、通常のNode.jsと同じように、例えばプロジェクトフォルダで以下のようにインストールします。

```sh
npm install request-promise --save
```

こうすることで、プロジェクトフォルダのpackage.jsonに当該モジュールの情報が書き込まれるので、ローカル開発でもデプロイ環境でもモジュールを扱うことができます。

## context.getTwilioClient()は使えない

管理コンソールからFunctionsを使う場合、context.getTwilioClient()でTwilio APIにアクセスできるようになりますが、Serverless Pluginを使う場合は残念ながら利用できません。
Severless Plugのテンプレートを作成した際に、.envにACCOUNT_SIDとAUTH_TOKENが生成されていますが、こちらは本来のAccountSidではなく、Serverlessの環境のために動的に生成されたAPIキーになります。
そこで以下のようにコーディングするのが現時点でのベストプラクティスです。

```javascript
const twilio = require('twilio');

const myAccountSid = 'ACxxxxxxxxxxxxxx'; // アカウントSIDを記載します
const apiKey = context.ACCOUNT_SID; // .envにデフォルトで記載されている値を使います
const apiSecret = context.AUTH_TOKEN; // .envにデフォルトで記載されている値を使います

const client = twilio(apiKey, apiSecret, { accountSid: myAccountSid });
```

# まとめ

今回はTwilio CLIを使って、サーバーレスの開発（FunctionsやAssets）をCLI経由で実現する方法をご紹介しました。

---
### Twilio（トゥイリオ）とは

https://twilio.kddi-web.com
Twilioは音声通話、メッセージング（SMS/チャット）、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウドAPIサービスです。初期費用不要な従量課金制で、各種開発言語に対応しているため、多くのハッカソンイベントやスタートアップなどにも、ご利用いただいております。