TwilioとReactでSMSを送信する方法

October 24, 2018
レビュー担当者

TwilioとReactでSMSを送信する方法

この記事はPhil Nashこちらで公開した記事(英語)を日本語化したものです。

SMSメッセージをWebアプリケーションから送信する方法はよく紹介されていますが、SMSメッセージをReactアプリケーションから送信することは可能でしょうか?実は、意外と簡単に実装できます。

本稿では、SMSメッセージをセキュアに送信するReactアプリケーションを構築する方法をご紹介します。

クライアント側からREST APIを使うべきでない理由

技術的には、JavaScriptのクライアント側アプリケーションから直接Twilio REST APIを使いSMSを送信できます。ただし、それを実際に実行すると、Twilio認証情報がサイトを使用中の他人に公開される可能性があります。悪意のあるユーザーは、その認証情報を悪用し、アカウントに高額な請求を発生させる可能性もあります。

ハッカーのイメージ
 

認証情報の悪用を避けるために、Twilio REST APIを実装し、認証情報を晒さずにSMSメッセージを送信する、サーバーサイドアプリケーションを作成します。次に、Reactアプリケーションからバックエンドを呼び出し、認証情報をインターネット上に公開せずに、SMSメッセージを送信します。

必要なツール

Twilio REST APIを使用し、アプリケーションからテキストメッセージを送信するには、以下の項目が必要になります。

  • Twilioのアカウント。Twilioホームページをブラウザで開き、今すぐ無料サインアップボタンをクリックするか、Twilioアカウントの作成リンクからサインアップします。このリンクを使用するとアカウントのアップグレード時に$10(米国ドル)相当分のクレジットが追加で付与されます。
  • SMSメッセージを送信できるTwilioの電話番号。
  • 最新バージョンのNode.js。サーバー側はどの言語でも構築できますが、本稿では使用する言語をJavaScriptに統一できるよう、Node.jsを使用します。
  • ブラウザ用のReact開発ツール(ツールの使用は任意ですが、アプリケーション内の状況を確認するのに非常に便利です)。

作業を開始するには、react-express-starterアプリケーションをダウンロードするかクローンします。このアプリケーションについては、以前のブログ記事で構築方法を説明しました。

git clone https://github.com/philnash/react-express-starter.git

ディレクトリに移動し、依存パッケージをインストールします。

cd react-express-starter
npm install

プロジェクトディレクトリに.envファイルを作成します。

touch .env

npm run devを実行し、プロジェクトが機能していることをテストできます。アプリケーションがlocalhost:3000でブラウザに読み込まれます。

このスターターアプリケーションは、同じプロジェクトにReactアプリケーションとExpressアプリケーションの両方があり、同時に実行できるように設定されています。この仕組みについて詳しくは、こちらのブログ記事をご覧ください。

サーバー側を構築する

Twilio APIをサーバーから呼び出す必要があります。Reactアプリケーションから呼び出せるエンドポイントをExpressサーバーに追加します。まず、Twilio Node.jsモジュールのインストールから始めます。

このアプリケーションでは、クライアント側の依存関係と切り離すために、サーバー側の依存パッケージをdevDependenciesとして保存します。

npm install twilio --save-dev

次に、アプリケーションにTwilio認証情報を設定する必要があります。Twilio ConsoleからTwilioアカウントのSID(Account SID)と認証トークン(Auth Token)、SMSメッセージを送信できるTwilio電話番号(My Twilio phone number)を確認します。この3つをすべて、作成した.envファイルに次のように入力します。

TWILIO_ACCOUNT_SID={あなたのAccount SID}
TWILIO_AUTH_TOKEN={あなたのAuth Token}
TWILIO_PHONE_NUMBER={Twilioの電話番号}

その結果、認証情報が環境に設定されますserver/index.jsを開き、メッセージの送信に必要なコードの作成を始めます。ファイルの先頭にある他のモジュールのrequireの下にTwilioライブラリをrequireし、環境変数から認証情報を取得して、ライブラリを初期化します。

const express = require('express');
const bodyParser = require('body-parser');
const pino = require('express-pino-logger')();
const client = require('twilio')(
  process.env.TWILIO_ACCOUNT_SID,
  process.env.TWILIO_AUTH_TOKEN
);

JSONとして構築したエンドポイントにデータを送信するため、JSON本文を解析できる必要があります。Body ParserのJSON Parserを使用し、次のようにExpressアプリを設定します。

const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(pino);

POSTリクエストのルートを作成します。/api/greetingのルート下に以下を追加します。

app.post('/api/messages', (req, res) => {
  
});

ここでは、JSONによる応答も行うため、Content-Typeヘッダーをapplication/jsonに設定します。

app.post('/api/messages', (req, res) => {
  res.header('Content-Type', 'application/json');
});

次に、初期化したTwilioクライアントを使用し、メッセージを作成します。Twilioの電話番号をfrom番号として使用し、受信したリクエスト本文からto番号とメッセージのbodyを取得します。これは、APIリクエストが成功すると実行され、失敗するとRejectされる、Promiseを返します。いずれの場合もJSONレスポンスが返され、リクエストが成功したかどうかをクライアント側に通知します。

app.post('/api/messages', (req, res) => {
  res.header('Content-Type', 'application/json');
  client.messages
    .create({
      from: process.env.TWILIO_PHONE_NUMBER,
      to: req.body.to,
      body: req.body.body
    })
    .then(() => {
      res.send(JSON.stringify({ success: true }));
    })
    .catch(err => {
      console.log(err);
      res.send(JSON.stringify({ success: false }));
    });
});

サーバー側に必要な作業はこれだけです。次は、Reactに取り掛かりましょう。

クライアント側を構築する

クライアント側では、サーバー経由でSMSを送信するフォームを、1つのコンポーネントに完全にカプセル化できます。

srcディレクトリにSMSForm.jsコンポーネントを作成し、ボイラープレートから始めます。

import React, { Component } from 'react';
  
class SMSForm extends Component {

}

export default SMSForm;

ここでは、電話番号とメッセージをユーザーが入力できるフォームを作成していきます。フォームが送信されると、詳細情報がサーバーのエンドポイントに送信され、メッセージがSMSとして番号に送信されます。

まず、このコンポーネントのrenderメソッドを構築しましょう。メソッドには、フォーム、電話番号の入力、メッセージのテキスト領域、送信ボタンを含めます。

  render() {
    return (
      <form>
        <div>
          <label htmlFor="to">To:</label>
          <input
            type="tel"
            name="to"
            id="to"
          />
        </div>
        <div>
          <label htmlFor="body">Body:</label>
          <textarea name="body" id="body"/>
        </div>
        <button type="submit">
          Send message
        </button>
      </form>
    );
  }

このフォームのスタイルを設定するためにCSSをします。src/SMSForm.cssファイルを作成し、次の内容を追加します。

.sms-form {
  text-align: left;
  padding: 1em;
}
.sms-form label {
  display: block;
}
.sms-form input,
.sms-form textarea {
  font-size: 1em;
  width: 100%;
  box-sizing: border-box;
}
.sms-form div {
  margin-bottom: 0.5em;
}
.sms-form button {
  font-size: 1em;
  width: 100%;
}
.sms-form.error {
  outline: 2px solid #f00;
}

SMSFormコンポーネントの先頭で、CSSをインポートします。

import React, { Component } from 'react';
import './SMSForm.css';

コンポーネントをsrc/App.jsにインポートし、renderメソッドを以下の内容に置き換えます。

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import SMSForm from './SMSForm';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />

          <SMSForm />
        </header>
      </div>
    );
  }
}

export default App;

npm run devを実行してアプリケーションを開始すると、ページにフォームが表示されます。

最終的なアプリのイメージ

現時点ではフォームは機能していません。修正しましょう。

Reactにてインタラクティブなフォームを作成する

HTMLフォームをコンポーネントに接続するには、次の処理が必要です。

  • inputtextareaのステートをコンポーネントのstate内で最新に保つ。
  • フォームの送信とサーバーへのデータ送信を処理する。
  • サーバーからのレスポンスを処理し、メッセージの送信が成功した場合はフォームをリセットし、送信が失敗した場合はエラーを表示する。

まず、コンストラクターの初期状態を設定します。フォームの入力内容、フォームが現在送信されているかどうか(送信ボタンを無効にできるようにするため)、エラーが発生したかどうかを保存する必要があります。コンポーネントのコンストラクターを次のように作成します。

class SMSForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: {
        to: '',
        body: ''
      },
      submitting: false,
      error: false
    };
  }

  // rest of the component
}

フォームフィールドの変更を処理し、状態を更新するためのメソッドが必要になります。1つはinput用、もう1つはtextarea用として、2つのメソッドを作成する方法も可能ですが、フォーム要素とstateの名前が一致しているため、1つのメソッドで両方に対応できます。

onHandleChange(event) {
  const name = event.target.getAttribute('name');
  this.setState({
    message: { ...this.state.message, [name]: event.target.value }
  });
}

ここでは、ES2015の計算プロパティ名を使用し、ステートの適切なプロパティを設定し、スプレッド演算子により、ステートにデータが 格納されます。

イベントの受信に使用する場合、thisが正しくなるよう、このメソッドとオブジェクトをバインドする必要があります。次の内容をコンストラクターの最後に追加します。

  constructor(props) {
    super(props);
    this.state = {
      message: {
        to: '',
        body: ''
      },
      submitting: false,
      error: false
    };
    this.onHandleChange = this.onHandleChange.bind(this);
  }

レンダリングされたJSXを更新し、現在のステートを使い、フォームフィールドの値を設定し、onHandleChangeメソッドにより、更新を処理します。

  render() {
    return (
      <form>
        <div>
          <label htmlFor="to">To:</label>
          <input
            type="tel"
            name="to"
            id="to"
            value={this.state.message.to}
            onChange={this.onHandleChange}
          />
        </div>
        <div>
          <label htmlFor="body">Body:</label>
          <textarea
            name="body"
            id="body"
            value={this.state.message.body}
            onChange={this.onHandleChange}
          />
        </div>
        <button type="submit">Send message</button>
      </form>
    );
  }

アプリを再読み込みすると、フォームフィールドを更新できるようになります。ブラウザ用のReact開発ツールがある場合は、stateの更新も確認できます。

コンソールでインスペクト

次に、フォーム送信の処理が必要になります。onSubmit関数を構築します。この関数は、 まずsubmitting状態プロパティをtrueに更新します。

Fetch APIを使用し、サーバーへのリクエストを行います。正常なレスポンスが返された場合は、フォームをリセットし、submittingfalseに設定します。エラーのレスポンスが返された場合は、submittingfalseを設定しますが、errortrueを設定します。

  onSubmit(event) {
    event.preventDefault();
    this.setState({ submitting: true });
    fetch('/api/messages', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(this.state.message)
    })
      .then(res => res.json())
      .then(data => {
        if (data.success) {
          this.setState({
            error: false,
            submitting: false,
            message: {
              to: '',
              body: ''
            }
          });
        } else {
          this.setState({
            error: true,
            submitting: false
          });
        }
      });
  }

onHandleChangeメソッドの場合と同様、このメソッドもコンストラクター内でバインドします。

  constructor(props) {
    super(props);
    this.state = {
      message: {
        to: '',
        body: ''
      },
      submitting: false,
      error: false
    };
    this.onHandleChange = this.onHandleChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

JSXにonSubmitメソッドをフォームの送信ハンドラーとして追加します。リクエストからエラーが返された場合は、フォームのクラスにもエラーを設定します。フォームの送信中に、ボタンのdisabledプロパティを設定します。

  render() {
    return (
      <form
        onSubmit={this.onSubmit}
        className={this.state.error ? 'error sms-form' : 'sms-form'}
      >
        <div>
          <label htmlFor="to">To:</label>
          <input
            type="tel"
            name="to"
            id="to"
            value={this.state.message.to}
            onChange={this.onHandleChange}
          />
        </div>
        <div>
          <label htmlFor="body">Body:</label>
          <textarea
            name="body"
            id="body"
            value={this.state.message.body}
            onChange={this.onHandleChange}
          />
        </div>
        <button type="submit" disabled={this.state.submitting}>
          Send message
        </button>
        </form>
    );
  }

必要な処理はこれだけです。

アプリを再度更新し、モバイル番号と送信するメッセージを入力します。

フォームを送信します。入力した情報に問題がなければメッセージが送信され、問題があった場合はエラーが表示されます。

最終的なアプリのイメージ
 

メッセージを送信し認証情報を安全に保つ

認証情報を公開せずに、SMSメッセージをReactアプリから送信できることは、非常に便利です。

このサンプルアプリケーションで使用しているすべてのコードは、GitHubリポジトリにて確認できます。

SMSメッセージを送信できるReactアプリの基礎が理解できたので、次のステップとして、さらにアプリに改善を加えてみてもよいでしょう。まず、入力数値の検証とエラーメッセージの改善が考えられます。同じような設計を使用し、電話番号検索通話2要素認証の実装をReactアプリから直接追加することもできます。

構築したアプリについて、感想をお寄せください。コメントは、Twitterで@philnashに投稿するか、メールでphilnash@twilio.comまでお送りください。