⚒️

todoアプリのモックを30分くらいでAWS CDK(with TypeScript)とNuxt.jsで作成する方法

2021/11/04に公開

はじめに

最近、自分でプロダクト開発を行ってみようかなと思っているので、「なるべく小さく動く」「他人と分担しやすい」フロントエンドとバックエンド環境を作る方法について棚卸ししてみました。

基本的な方針として、なるべくブラックボックスを作らないようにします。
例えば、 create-nuxt-app を使うことで使わないディレクトリを作ったりはなるべくやらずに、 npm i --save nuxt mkdir pages という感じで作り上げていく感じですね。ただ、cdk init は使います。

とりあえずCDKとかNuxt.js始めてみたいけど、なるべく小さく始めてみたい、という人の参考になれば、と思います。
30分くらいあればこの記事でやったことは出来ると思います。

結論

こんな感じでやれます
https://youtu.be/yBFvp70zKxQ

この記事を書くにあたってコーディングしてみた模様を動画で公開しているものです
記事の内容とは若干違うのですが、雰囲気を掴む手助けにはなるかと思います。

前提

  • AWSアカウントを所有していること
  • CDKのCLIが利用できる状態になっていること
    • デフォルト、もしくは何かしらのプロファイル設定なども済んでいること(つまりいきなりcdk initとかできる状態になっていること)
  • npmが利用できる状態になっていること
  • CDKにはTypeScriptを利用する
  • Nuxt.jsはSPAモードで動作する

やってみたこと

とりあえずLambdaが動くようにする

CDKのスケルトン作成&初期設定

  • CDKのスケルトンを作成&必要なライブラリのインストール
mkdir back
cd back
cdk init --language typescript
npm init
cdk bootstrap
npm run watch
  • ビルド&デプロイ
npm run build; cdk deploy

ここまでで、とりあえずスタックが作成された状態になります。

Lambdaのアプリケーションコードを書く

  • 必要なライブラリやファイルを追加
npm install --save @aws-cdk/aws-lambda
mkdir -p src/lambda/
code src/lambda/index.ts
src/lambda/index.ts
export const handler = async ( event:any = {} ) : Promise<any> => {
  const res = {
    content: { "id": "1", "task": "dummy" }
  }
  return {
    statusCode: 200,
    body: JSON.stringify(res),
    headers: {
      "Content-Type":"application/json",
      "Access-Control-Allow-Headers": "Content-Type",
      "Access-Control-Allow-Origin": '*',
      "Access-Control-Allow-Methods": "OPTIONS,GET"
    }
  }
}

LambdaのCDKコードを書く

  • CDKのコードを修正
code lib/back-stacks.ts
lib/back-stacks.ts
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';

export class TestCdk1Stack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // lambda
    const getTodos = new lambda.Function(this, "getTodosLambda", {
      runtime: lambda.Runtime.NODEJS_14_X,
      handler:"index.handler",
      code: lambda.Code.fromAsset("src/lambda")
    })
  }
}
npm run build; cdk deploy

とりあえず、ここまでで次を返すLambda関数ができました

content: { "id": "1", "task": "dummy" }

REST API化(API Gateway経由でLambdaを叩けるようにする)

LambdaをHTTP経由で実行し、結果を得られるようにしましょう。

npm install --save @aws-cdk/aws-apigateway
code lib/back-stacks.ts
lib/back-stacks.ts
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import * as apigateway from '@aws-cdk/aws-apigateway';

export class TestCdk1Stack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // lambda
    const getTodos = new lambda.Function(this, "getTodosLambda", {
      runtime: lambda.Runtime.NODEJS_14_X,
      handler:"index.handler",
      code: lambda.Code.fromAsset("src/lambda")
    })

    // apigateway
    const getTodosApi = new apigateway.RestApi(this, "getTodosApi", {})
    getTodosApi.root.addMethod("GET", new apigateway.LambdaIntegration(getTodos));
  }
}
npm run build; cdk deploy

cdk deployの実行結果にAPI GatewayへのURLが入ってきます。そのURLを叩くと getTodosLambda の実行結果が返ってくるというモックAPIが出来上がった状態になります。

とりあえずNuxt.jsが動くようにする

バックエンドのモックができたのでフロントエンドから呼び出せるようにしていきましょう。
なるべく段階を踏んで。

最低限起動するだけ

mkdir front
cd front
npm init
code package.json

package.jsonのscripts.devに "dev": "nuxt --spa" を追加

package.json
{
  "name": "front",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \\"Error: no test specified\\" && exit 1",
    "dev": "nuxt --spa"
  },
  "author": "",
  "license": "ISC"
}
npm i --save nuxt
mkdir pages
touch pages/index.vue
code pages/index.vue
pages/index.vue
<template>
  <div>
    Hello Nuxt.
  </div>
</template>
npm run dev

これで、とりあえず「Hello Nuxt」と表示するだけの静的ページが作成できた状態になります。

イベントハンドラを設定してみる

次に、動的なサイトにしていく一歩を踏み出します。
ボタンを設置して、押したらページの表示が切り替わるようにします。

  • イベントハンドラを追加する
code pages/index.vue
pages/index.vue
<template>
  <div>
    <button v-on:click="clicked">click me</button>{{text}}
  </div>
</template>
<script>
export default {
  data(){
    return {
      text: "hoge"
    }
  },
  methods: {
    clicked: function() {
      this.text = "huga"
    }
  }
}
</script>
npm run dev

これで、とりあえずボタンでリアクティブに内容が切り替わる動的なページが作成できました。

APIを叩くようにしてみる

次に、外部とのデータを入出力できるように、つまりAPIを利用できるようにしていく一歩を踏み出します。
ただ、APIがおかしいのかフロントエンドの実装がおかしいのか切り分けるために、インターネット上で公開されているAPIモックを利用して、フロントエンドの実装だけ作りましょう。

  • モックAPIを叩くようにする(with JSONPlaceHolder)
code pages/index.vue
pages/index.vue
<template>
  <div>
    <button v-on:click="clicked">click me</button>{{text}}
  </div>
</template>
<script>
export default {
  data(){
    return {
      text: "hoge"
    }
  },
  methods: {
    clicked: async function() {
      fetch(url, { mode: 'cors' })
	await new Promise( (resolve) => {
          setTimeout( () => {
            resolve();
          } , 3000)
        } )
        await fetch('https://jsonplaceholder.typicode.com/todos/1')
          .then(response => response.json())
          .then(json => this.text = json)
    }
  }
}
</script>
  • URLを差し替えて見る(さっき作ったオリジナルのヤツ)
code pages/index.vue
pages/index.vue
<template>
  <div>
    <button v-on:click="clicked">click me</button>{{text}}
  </div>
</template>
<script>
// デプロイしたAPI GatewayのURL
const url = "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/todos"; // 先程作成したAPI GatewayのURLを入れる

export default {
  data(){
    return {
      text: "hoge"
    }
  },
  methods: {
	clicked: async function() {
            await fetch(url)
                .then( response => response.json() )
                .then( json => this.items = json)
        },
  }
}
</script>

これで、インターネット上のデータをやりとりできる動的なページが作成できました。

おわりに

今まではいろいろなサイトをググったりしてましたが、面倒なのでよくやることを記事化して一本化してみました。

だれかの参考になれば幸いです。
自分が使いそうだけど。

Discussion