この記事は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フォームをコンポーネントに接続するには、次の処理が必要です。
input
とtextarea
のステートをコンポーネントの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を使用し、サーバーへのリクエストを行います。正常なレスポンスが返された場合は、フォームをリセットし、submitting
をfalse
に設定します。エラーのレスポンスが返された場合は、submitting
にfalse
を設定しますが、error
にtrue
を設定します。
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までお送りください。