Cloud9とSAMでAWSローカル開発環境を爆速構築するわよ(Angular7とLambdaを連結)

# はじめに
Qiitaコントリビューターの皆様いつも大変お世話になっております。Lovedです。
今回はAWSのCloud9環境上で、Webアプリのローカル開発環境を立てていきたいと思います。
AWSではLambdaの実行毎に課金されるので、ローカル上に環境を再現できれば低コストで開発を進める事ができ効率的です。
今回も例によって、間違っている点等がございましたらお手柔らかに御教示頂ければ幸いです。

内容にはもはやn番煎じな部分も多少ありますが、困っている人の役に少しでも立つのならなんでもいいです。
**アウトプットは正義、集合知は大義**。そう信じましょう。~~(圧すごい~~
# 対象読者
・AWS上でWebサービスを開発しているが、テストやデバッグが工数・お金的にコスト高で困っている な方
・とりあえず画面とAPIをさくっと連結させて爆速でWebサービスのプロトタイプを作り、いじって遊びたい な方  
# TL;DR
sam-localのTemplate.yamlに適切な設定を記載し、
画面アプリとAPIGatewayを別ポートでそれぞれ実行させれば実現可能です。
# Cloud9環境作成後~Lambdaローカル実行,SAM-LOCAL-APIGateway構築
Cloud9環境にログインします。
Cloud9ですが、インスタンスサイズはt2.smallを推奨します。(Angularビルドにマシンリソースを食う為)
まずはNodeとsam-localを更新していきます。
記事投稿現在、sam-localのバージョンによっては動作に失敗するので、注意してください。
sam事始めにつきましては、[こちらの投稿](https://qiita.com/hayao_k/items/841026f9675d163b58d5#sam-init-%E3%82%92%E8%A9%A6%E3%81%99)が非常にわかりやすいのでオススメです。

```bash
nvm install v10.15.3
npm uninstall -g aws-sam-local
pip install --user aws-sam-cli==0.15.0
sam --version
# SAM CLI, version 0.15.0
sam init --runtime nodejs
```
APIの中身を書き換えていきます。

```typescript:app.js
// 抜粋
        response = {
            'statusCode': 200,
            'body': JSON.stringify([{
                message: 'hello world',
                play: 'StarCraftII',
                lol: ['Ahri is too Cute.','Elise is Nice.'],
                best_of_finalfantasy: 8,
                gfriend: 'nice_yuju'
            }]), 
            'headers': { 
            "Content-Type": 'application/json',
            "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
            "Access-Control-Allow-Methods": "GET",
            "Access-Control-Allow-Origin": "https://XXXXXXXXXXXX.vfs.cloud9.ap-northeast-1.amazonaws.com",
            "Access-Control-Allow-Credentials":"true"
            }
        }
```

これだけでまずはLambdaのローカル実行テスト、デバッグが出来ます。

```bash
# ローカルで実行
sam local invoke HelloWorldFunction --event event.json
#  {"statusCode":200,"body":"{\"message\":\"hello world\",\"play\":\"StarCraftII\",\"lol\":[\"Ahri is too Cute.\",\"Elise is Nice.\"],\"best_of_finalfantasy\":8,\"gfriend\":{\"nice\":\"yuju\"}}","headers":{"Access-Control-Allow-Origin":"*"}}
sam local start-api -p 8081
# new terminalで新しいタブを開き実行
curl localhost:8081/hello
#  {"message":"hello world","play":"StarCraftII","lol":["Ahri is too Cute.","Elise is Nice."],"best_of_finalfantasy":8,"gfriend":{"nice":"yuju"}}

```
# Angular7プロジェクト作成~プレビューモード起動
[拙者の前回記事](https://qiita.com/lovedofPearl/items/3fbf90cf3ff5ae245c08)
で使用したプロジェクトを流用しましょう。
「前準備」の欄に手を加えながらAngular7プロジェクトを作っていきます。
下部には使用したファイルの中身を一部掲載しますので、ハンズオンする際の参考にしてください。

```bash
# 任意のディレクトリからスタート
nvm use v10.15.3
npm install -g @angular/cli
ng new my-app --style scss --routing false
cd my-app
npm install --save ag-grid-community ag-grid-angular
# app.component.ts等を編集(下部に各ファイルを提示してあります。)
# プレビューモードを起動
ng serve  --port=8080 --disableHostCheck=true
```
Cloud9画面上部「Run」ボタン左「Preview」から「Preview Running Application」を押下すると、
ブラウザのようなタブが開きます。
そこで表示されているURLをコピーし、Chromeの別タブに張り付けることで、
通常のChromeのようにアプリケーションのプレビューを確認することができます。
**このときのURL (https://XXXXXXXXXXX.vfs.cloud9.region.amazonaws.com) を控えておきます。**

Angularプロジェクトの各ファイルを掲載します。

```html:app.component.html
<div style="text-align:center">
<ag-grid-angular 
    style="width: 800px; height: 200px;" 
    class="ag-theme-balham"
    [rowData]="rowData | async" 
    [columnDefs]="columnDefs"
    >
</ag-grid-angular>
</div>
```

```typescript:app.component.ts
import { Component } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'my-app';
  
      columnDefs = [
        {headerName: 'message', field: 'message' },
        {headerName: 'play', field: 'play' },
        {headerName: 'lol', field: 'lol' },
        {headerName: 'best_of_finalfantasy', field: 'best_of_finalfantasy' },
        {headerName: 'gfriend', field: 'gfriend' },
    ];

    rowData :any;
    
    constructor(private http: HttpClient) {}

    ngOnInit(){
      const url = "https://XXXXXXXXXXX.vfs.cloud9.ap-northeast-1.amazonaws.com:8081/hello";
      this.rowData=this.http.get(url, {withCredentials:true});
    }
    
}

```

```typescript:app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { AgGridModule } from 'ag-grid-angular';// ←追加
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,AgGridModule.withComponents([]),HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

```

```yaml:template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app
Globals:
  Api:
    Cors:
      AllowMethods: "''"
      AllowHeaders: "''"
      AllowOrigin: "'*'"

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs8.10
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: ANY
            Cors:
              AllowMethods: "''"
              AllowHeaders: "''"
              AllowOrigin: "'*'"
              AllowCredentials: "'true'"
Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

```

# Angular画面とAPIの連結(本題)
「先ほど控えておいた、プレビューモード時のURL+LocalApiGateway側のポート、パス」をAngularアプリからのAPI発行先URLとして登録します。
ここまでの設定、手順が出来ていれば、プレビューモードの画面から発行したAPIの戻り値がag-gridにセットされているはずです。
本題と銘打っておきながらあっさりですが、これでCloud9を使用してローカル開発環境を構築できました。
ううむ、段取り八割。キャッキャッ

# ハマりPoint
・Cloud9環境に入る際は、ChromeのAdblok拡張等は停止しておきましょう。(起動せず永遠に固まりました)
・ポート番号は8080,8081,8082を使用しましょう。詳細はAWS公式ドキュメントを御確認下さい。
・ポートが違うだけでもOriginは異なります。適切なCORS対策を施しましょう。(クッキー使用時はクライアント側でも対応が必要です。)

# まとめ
Cloudformation拡張であるSAMはYamlファイルの記述がほぼ同じですので、こちらを掘り下げる事により更なる構築が可能です。
カスタムオーソライザーを噛ませたり、空いたポートを使用してローカルRDS接続なんかもできると思います。
目指せ、自前LocalStack!! ですね？

今後もインフラのコード化は進行していくでしょうから、
開発者はインフラに、インフラは開発者に歩み寄りながら互いに理解を深めていく事が大切です。

御観覧頂きまして、ありがとうございました。


CM:今後とも、株式会社TrueSeekerSをどうぞよろしくお願い申し上げます。