ReactとAWS API Gatewayの連携方法

はじめに

こんにちは。OfferBoxの中の人の串上です。

最近サーバーレスとかいうものが注目されつつありますよね。 個人でWebアプリ作ったりするのが好きな人にとって必ずネックになるのがサーバーの設計ですが、 それを一気に解決してくれるのがサーバーレスではないかなと個人的に思っています。

さすがに個人でAWSのEC2を2つ立てて、RDS借りて、ALB立てて。。 っていわゆるAWSの最低限の構成ってやつをやると一気にコストかかって、やる気が削がれてしまいますよね。

かといってこれからの時代にVPS借りてっていうのもなんか違う感じがして、気持ち的に嫌ですよね。

そんなときに解決してくれるのが、AWSのS3、API GatewayとLambda、DynamoDBだと思います。 それぞれの詳細はこちら。

※S3 https://aws.amazon.com/jp/s3/

API Gateway https://aws.amazon.com/jp/api-gateway/

※Lambda https://aws.amazon.com/jp/lambda/

※DynamoDB https://aws.amazon.com/jp/dynamodb/

簡単に説明すると、API GatewayはアマゾンさんがAPIサーバーを提供してくれて、 APIサーバーの中の処理をLambdaで実行して、 ユーザーが入力したデータなどはLambdaからDynamoDBに貯めたり、参照したりして、 S3にHTML、CSS、JSを配置してWebアプリを作ってしまおうということです。

上記のメリットとしては、以下のようなものが挙がられるのかなと思っています。

  • EC2等を立てるよりも、初期導入時のコストが安い(リクエスト数等による)

  • S3の99.999999999%の耐久性とAPI Gatewayの自動スケール機能による個人で開発するには最強すぎるスケーラブルなアーキテクチャを実現可能

  • アマゾンさんが大体のセキュリティをやってくれているのであまり考えなくていい。

デメリットももちろんあります。

  • サーバーの設定をごにょごにょしたい人には物足りない。(というかほとんどできない)

  • DynamoDBをメインのデーターベースとして使う場合は、ころころと仕様が変わるサービスは作れない。(インデックスを途中で変えたり、足したり出来ない) ※お金出せばちょっとなら足せます。(5つまで)

  • RubyとかGoとかPHPとかで書きたいって思っても出来ない。(NodeかPythonJavaでしかLambdaは実装できない)

他にもいろいろありますが、長くなるので割愛します。

いよいよ本題に入りますが、そんなサーバーレスの根幹となる ReactとAWS API Gatewayの連携方法をご紹介します。※Macユーザー向け

(ReactじゃなくてもAngularとかBackboneでもいけますが、そちらを使いたい場合はAPI Gatewayの部分だけ参考にしてください。)

Nodeのインストール

まずは、Nodeをインストールします。 (普通にインストールしてもいいんですが、バージョンが上がったときにすぐに対処できるようにnvm(Node Version Manager)インストールします。) ターミナルを立ち上げて以下を実行してください。

nvmのインストール

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.0/install.sh | bash

これを実行するとホームディレクトリに.nvmフォルダが作成されます。

さらに~/.bash_profile, ~/.zshrc, ~/.profile, ~/.bashrcのいずれかに以下を追記して保存します。

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"

保存し終わったら、以下のようにして追記した設定をすぐに反映させます。

source ~/.bashrc

ここでnvmがインストールされたか確認します。

nvm --version

を実行して以下のように表示されればnvmのインストール完了です。

0.32.0

nvmがインストールされたのでいよいよNodeのインストールを行います。

Nodeのインストール

ひとまず最新のものをインストールしましょう。 バージョン指定などもできますが、詳しくは公式HPを参照してください。

https://github.com/creationix/nvm

nvm install node
nvm use node

上記を実行するとNodeがインストールされるので、以下を実行してインストールの確認を行って下さい。 (node, npm)

node --version
v6.4.0
npm --version
3.10.3

上記のようにバージョンが表示されればNodeのインストールは完了です。

Reactでフロントを作成

Reactを使用する準備が整ったので、早速フロント用のスクリプトを作成していきます。

各種インストール

# フロントスクリプト用ディレクトリ作成
mkdir sample_front
cd sample_front
# reactをインストール
npm install react react-dom
# APIと連携するためにaxiosもインストール
npm install axios

フロントスクリプトを作成

まずsample_frontディレクトリの構成は以下のようにしたいと思います。 少し補足しておくと、Reactは事前にコンパイルが必要なため、コンパイル前のjsとコンパイル後のjsでディレクトリを分けています。

.
├── assets
│   └── js
│       └── bundle.js #コンパイルされたjs
├── index.html
└── src
    └── js
        └── app.js #コンパイル前のjs

index.htmlの中身は以下のようにします。

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <div class="container"></div>
    </body>
    <script src="assets/js/bundle.js"></script>
</html>

上記の例ではsrc/js/app.jsの中にスクリプトを実装していき、 assets/js/bundle.jsにjsをコンパイルするようにしています。

コンパイルってどうするの?って疑問には以下でお答えします。

コンパイルするための方法はいろいろありますが、今回はwebpackというものを使っていきます。

まずはwebpackをインストールしましょう。 webpackコマンドを使用できるようにしたいので、-gオプションをつけてインストールします。

npm install -g webpack

またこれらも必要なので何も難しいことは考えずにインストールしてください。

npm install babel-loader babel-core babel-preset-react babel-preset-es2015

上記を実行するとwebpackがインストールされるので、 sample_frontディレクトリの直下に以下のような内容のwebpack.config.jsを作成します。

module.exports = {
    entry: [
        './src/js/app.js'
    ],
    output: {
        path: __dirname + '/assets/js/',
        filename: 'bundle.js'
    },
    module: {
        loaders: [{
            exclude: /node_modules/,
            loader: 'babel-loader',
            query: {
                presets: ['es2015', 'react']
            }
        }]
    }
};

以上の設定が終わったら、早速src/js/app.jsを作成します。 以下のような内容で保存してください。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';

const API_BASE_URL = '';

class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            message: ''
        }
    }
    componentWillMount() {
        // コンポーネントがマウントされる前にデータを取得する
        this.getDataFromApi();
    }
    getDataFromApi() {
        // APIをコール
        axios.get(API_BASE_URL)
            .then((response) => {
                // APIから取得したデータをstateに保存
                this.setState({
                    message: response.data.message
                });
            })
    }
    render() {
        return (
            <div>
                {this.state.message}
            </div>
        );
    }
}

// index.htmlの.containerに描画する
ReactDOM.render(<App />, document.querySelector('.container'));

これでReactの準備は整ったので、次はAPI Gatewayの設定を行います。 AWSのアカウントは事前に取得しておいてください。

API Gatewayの作成

AWSのコンソールにログインして「サービス」の中から「API Gateway」を選択すると以下のような画面が表示されるので、 「今すぐ始める」ボタンをクリックします。

f:id:i-plug-develop:20161011165551p:plain

すると以下のような画面が表示されますが、ひとまず「OK」をクリックします。

f:id:i-plug-develop:20161011165716p:plain

ダイアログが閉じたら「新しいAPI」をクリックし、以下のように入力して「APIの作成」ボタンをクリックします。

f:id:i-plug-develop:20161011165741p:plain

すると以下のような画面が表示されるので、早速エンドポイントを作成していきましょう。

今回は単純な例なので、早速GETメソッドを作成します。

「アクション」ボタンをクリックして、「メソッドの作成」をクリックしましょう。

すると以下のような画面になるので、表示されたプルダウンから「GET」を選択して、プルダウンの右にあるチェックボタンをクリックします。

f:id:i-plug-develop:20161011165837p:plain

少し待つと、以下のような画面が表示されるので、「統合タイプ」は「Lambda関数」を選択して、「Lambdaリージョン」は「ap-northeast-1」※ を選択します。 ※リージョンは好きなのをどうぞ。

すると、「ap-northeast-1 内にLambda 関数 がありません. Lambda 関数を作成します」というアラートが表示されるので、「Lambda関数を作成します」をクリックします。

f:id:i-plug-develop:20161011165926p:plain

以下の様な画面が開くので、「All runtimes」のプルダウンからPython2.7を選択して、「Filter」に「hello」を入力します。

※NodeやJavaが好きな人はそちらでやってもらっても大丈夫です。

入力したら、「hello-world-python」というのが表示されるので、タイトルをクリックしてください。

f:id:i-plug-develop:20161011165950p:plain

以下のような画面が表示されるので、「Next」をクリックしてください。

f:id:i-plug-develop:20161011170034p:plain

以下の様な画面が表示されるので、

f:id:i-plug-develop:20161011170122p:plain

「Name」に「helloWorld」を入力し、 「Lambda function code」に以下を入力します。

from __future__ import print_function

import json

print('Loading function')


def lambda_handler(event, context):
    response = {}
    response["message"] = "Hello World"
    return response

また画面をスクロールして、以下の画像のように

f:id:i-plug-develop:20161011170159p:plain

「Role name」に「helloWorldRole」を入力して、 「Next」ボタンをクリックします。 ※その他の設定はひとまずデフォルトでいきます。

すると以下のような画面になるので、「Create function」をクリックします。

f:id:i-plug-develop:20161011170249p:plain

以上でLambdaの作成は完了です。

再度サービスメニューから「API Gateway」を選択します。

API Gatewayの画面が開いたら、「HelloWorldApi」をクリックします。

先ほど作成した「GET」メソッドをクリックしてください。 以下のような画面が表示されるので、

f:id:i-plug-develop:20161011170512p:plain

「Lambdaリージョン」を「ap-northeast-1」に変更し、「Lambda関数」に「helloWorld」を入力してください。

すると「保存」がクリックできるようになるので、「保存」をクリックします。

次に以下のようなダイアログが表示されるので、「OK」をクリック。

f:id:i-plug-develop:20161011170548p:plain

すると以下のような画面が表示されるので、早速APIのテストを行いましょう。

「クライアント」の上のほうに表示されている「テスト」をクリックします。

f:id:i-plug-develop:20161011171605p:plain

以下のような画面になるので、「テスト」をクリック。

f:id:i-plug-develop:20161011170627p:plain

以下の表示表示されたらテスト成功です! レスポンス本文にHello Worldが返されているのがわかります。

f:id:i-plug-develop:20161011170646p:plain

ただし、このままではまだ外部からアクセス出来ないので、「アクション」メニューから「CORSの有効化」をクリックします。 すると以下のような画面が表示されるので「CORSを有効にして既存のCORSヘッダーを置換」をクリックします。

f:id:i-plug-develop:20161011170711p:plain

次に以下のようなダイアログが表示されるので、「はい、既存の値を置き換えます」をクリック。

f:id:i-plug-develop:20161011170726p:plain

次に作成したAPIをデプロイしてURLを生成します。

「アクション」メニューから「APIのデプロイ」を選択してください。 選択すると以下のようなダイアログが表示されるので、以下のように入力して「デプロイ」をクリックします。

f:id:i-plug-develop:20161011170746p:plain

以下のような画面が表示されたらAPIの作成は完了です!

赤枠の部分にAPIのURLが表示されているので、このURLをコピーします。 次にReactでAPIのURLを設定します。

f:id:i-plug-develop:20161011170802p:plain

ReactでAPIのURLを設定

src/js/app.jsを開いて、API_BASE_URLにコピーしたURLを貼り付けます。

Reactをコンパイルしてブラウザで確認

このままではまだassets/js/bundle.jsは作成されていないので、sample_frontディレクトリ直下で以下のコマンドを実行します。

webpack

するとassets/js/bundle.jsが生成されるので、index.htmlをブラウザで開いて下さい。

以下のように「Hello World」が表示されたら完了です!

お疲れ様でした。

f:id:i-plug-develop:20161011170825p:plain

思ったより長くなりましたが、だいたいこんな流れで連携できます。

本当はAPI Gatewayの設定などもコマンドラインからできるのですが、最初なので画面からの作成方法を説明しました。

APIの数が多くなるといちいち画面で一つずつ作成してられないので、Serverless Frameworkという便利なフレームワークがあるのですが、

そちらについてはまたおいおい書きたいと思います。

serverless.com

また、実際は作成したフロントのスクリプトをS3にアップロードしてWebホスティングを有効にして外部からアクセスできるようにしたり、 Lambdaの中でDynamoDBに接続してデータを取ってきたりしないと本格的なWebアプリは作成できませんが、 今回はひとまずさわりの部分だけご紹介しました。

また時間があればその辺も書きたいと思います。