Single Page Application を Serverless Framework と React でやる Tutorial(4)

前回までで local で WEB API を開発するやり方がぼんやりと見えてきたと思います。
今回は S3 に React を配備して SPA を Serverless Framework で使用する場合の開発環境を考えます。


React の雛形と、 Serverless Framework の雛形をそれぞれ作成します。

```
mkdir  serverless-tutorial-four && cd $_
create-react-app front
sls create --template aws-nodejs --path server
```

とりあえず `GET http://localhost:3000/` に対して `現在時刻` を返す API を作っておきます。

```
cd server
yarn init -y
yarn add moment express serverless-http --save
yarn add serverless-offline serverless-finch --dev
```

`serverless.yml` を修正します。`custome.client.bucketName` に関しては各自で S3 のバケットを生成してください。

```
service: server

plugins:
 - serverless-finch
 - serverless-offline

custom:
  client:
    bucketName: [your-bucket-name]
    distributionFolder: ../front/build

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  region: ap-northeast-1

functions:
  app:
    handler: handler.main
    events:
      - http:
          method: ANY
          path: '/'
          cors: true
      - http:
          method: ANY
          path: '{proxy+}'
          cors: true
```

handler.js を修正します。

```
const serverless = require('serverless-http');
const express = require('express');
const moment = require('moment');
const app = express();

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*")
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS")
  next()
});

app.get('/api/now', function (req, res) {
  res.json({ now: moment().format('YYYY-MM-DD HH:mm:ss') }); 
});

module.exports.main = serverless(app);
```

`sls offline --port 4000` を実行した後下記コマンドを実行します。

```
% curl "http://localhost:4000/api/now"
{"now":"2018-04-06 21:21:31"}
```

## 本題

以下を実行します。先ほど serverless の port を 4000 に指定していると以下のコマンドでは port 3000 で React の開発サーバーが起動します。

```
cd front
yarn add axios --save
yarn start
```

ということで `package.json` に以下を追記します。

```
  "proxy": {
    "/api": {
      "target": "http://localhost:4000"
    }   
  }
```

この proxy は `react-scripts@0.2.3` より Development モードで動作するようになっています。

https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#proxying-api-requests-in-development

## 実装

`front/src/App.js` を修正します。

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

const BASE_URL = '/';

class App extends Component {

  constructor(props) {
    super(props)
    this.state = { now: "" }
  }

  componentWillMount() {
    axios.get(`${BASE_URL}/api/now`).then((res) => {
      console.log(res);
      this.setState({now: res.data.now});
    })
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <h2>Now: {this.state.now}</h2>
      </div>
    );
  }
}

export default App;
```

http://localhost:3000/ を開いて動作確認をして下さい。

## デプロイする

- Function と Endpoint をデプロイ
- S3 をデプロイ

### Function と Endpoint をデプロイ

おもむろに以下のコマンドを実行します。

```
% sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (6.95 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................................
Serverless: Stack update finished...
Service Information
service: server
stage: dev
region: ap-northeast-1
stack: server-dev
api keys:
  None
endpoints:
  ANY - https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/
  ANY - https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+}
functions:
  app: server-dev-app
```

`curl https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/api/now` を実行すると現在時刻が返ってくると思います。

というわけで React App を build するときにこの長い URL を伝える方法を考えないといけないわけです。

## process.env.REACT_APP_GW_URL

serverless-plugin-build-create-react-app のような方法もあるようですが、ここではシンプルに以下のようにしました。

`src/App.js` の BASE_URL の定義を下記のようにします。

```
// const BASE_URL = '';
const BASE_URL = process.env.REACT_APP_GW_URL ? process.env.REACT_APP_GW_URL : '';
```

その後 serverless deploy した時に表示された API Gateway の URL を設定します。

```
export REACT_APP_GW_URL="https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev"
yarn build
```

これで `front/build/` 配下に API Gateway にアクセスする React App　が生成されます。
これを S3 にアップロードしましょう。

## serverless-finch

serverless-s3-deploy が Deprecated になっているのでこちらを使用します。
適当な Bucket を作成してください。Bucket を作成する方法はここでは割愛します。

server のディレクトリへ移動してから `serverless client deploy` を実行すると
表示された Bucket の URL を開くとローカルで作成した React App が動作していると思います。

## まとめ

今回は S3 + API Gateway + Lambda の example を作成しました。 dynamoDB に関しては含めて説明をしませんでしたが前回の Tutorial で説明済みのため割愛しました。
次回は総集編として以前作成した React tutorial で作った fav-articles を Serverless Framework　で作成したコードを紹介します。