Getting Started With Serverless and TypeScript

用意するもの

  • Node.js
    • あんまり古くないやつ

Hello World まで

repo を作る

$ mkdir oreno-sls-ts
$ cd oreno-sls-ts
$ git init

依存物をインストール

$ npm init
$ npm install --save-dev serverless serverless-webpack ts-loader tslint typescript webpack

今回はあえて serverlessをグローバルに入れないパティーンでやってみる。package.jsonserverlessのバージョンを管理できるメリットがある(serverless.yml の中にバージョン固定する設定もできるけど)。

雛形を作り、いらないものを一旦捨てる

$ node_modules/.bin/serverless create --template aws-nodejs
$ node_modules/.bin/tslint --init
$ rm handler.js serverless.yml # このあと手動でつくる

いろいろ作る

.gitignore

node_modules
.serverless
.built

serverless.yml

service: oreno-sls-ts
provider:
  name: aws
  runtime: nodejs4.3
plugins:
- serverless-webpack
functions:
  hello:
    handler: index.hello
    events:
    - http:
        path: hello
        method: get

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs"
  },
  "exclude": [
    "node_modules"
  ]
}

webpack.config.js

var path = require('path');

module.exports = {
  entry:  './index.ts',
  target: 'node',

  module: {
    loaders: [
      {
        test:   /\.ts(x?)$/,
        loader: 'ts-loader',
      },
    ],
  },

  resolve: {
    extensions: [
      '.ts',
      '.js',
      '.tsx',
      '.jsx',
      '',
    ],
  },

  output: {
    libraryTarget: 'commonjs',
    path:          path.join(__dirname, '.built'),
    filename:      'index.js',
  },
};

index.ts

export * from './functions/hello';

functions/hello.ts

export function hello(event, context, callback): void {
  const response = {
    statusCode: 200,

    headers: {
    },

    body: JSON.stringify({
      "message": "Hello TypeScript!!!!"
    })
  };

  callback(null, response);
};

ローカルで動作確認

$ node_modules/.bin/serverless webpack serve

うまく動いていればhttp://localhost:8000/helloで動作確認できるはず。

デプロヰ

$ node_modules/.bin/serverless deploy
$ curl https://********.execute-api.us-east-1.amazonaws.com/dev/hello | jq

うまくいけば {"message": "Hello TypeScript!!!!"} が表示されるはず。

せっかくの TypeScript なので

型定義を使ってみる

Lambda Function に渡される context オブジェクトを型定義してみます。 event はめちゃくちゃ長くてアレだったので、ちゃんと書くときには定義します…

types.d.ts

export interface LambdaExecutionContext {
  callbackWaitsForEmptyEventLoop: boolean
  logGroupName:                   string
  logStreamName:                  string
  functionName:                   string
  memoryLimitInMB:                string
  functionVersion:                string
  invokeid:                       string
  awsRequestId:                   string
  invokedFunctionArn:             string
}

さっきの hello function を少し変更してこれを使ってみます。

functions/hello.ts

import {LambdaExecutionContext} from '../types';

export function hello(event, context: LambdaExecutionContext, callback): void {
  const response = {
    statusCode: 200,

    headers: {
    },

    body: JSON.stringify({
      "message": `Hello!!!! I am a function named "${context.functionName}"`
    })
  };

  callback(null, response);
};

さてさて型定義されているということは。 Visual Studio Code なら補完が効きますし、

55ff5ee8-cb8b-a7eb-0b59-537c8ed602a4

ありえないプロパティを指定するとちゃんとエラーを指摘してくれます。

c81ed0b0-5406-f05c-4566-cad3eef492a2

すてき!!!!!

AWS SDK で async/awaitしてみる

パッケージを追加する。

$ npm install --save-dev aws-sdk @types/aws-sdk json-loader

@types/aws-sdkDefinitelyTyped にある AWS SDK 用の型定義。 TypeScript の型定義管理はちょうざっくり変遷を辿ると

1.tsd っていう型定義管理ツールがあるぞ
2.tsd はオワコン。 typings の時代だぜ
3.型定義インストール専用のツールなんてもういらん。npm@types/fooって入れればおk

…という流れらしく、従ってnpmで入れる。

json-loader は AWS SDK の中で require('foo.json') してるの(たぶん)を Webpack で処理するのに必要。

してるの(たぶん)を Webpack で処理するのに必要。

ちょっと昔は AWS SDK と Webpack の組み合わせに問題があり、みんな困ってたけど直った模様。マジ感謝。

index.ts

export * from "./functions/hello";
export * from "./functions/listfunctions";

functions/listfunctions.ts

import * as AWS from "aws-sdk";
import {LambdaExecutionContext} from "../types";

const lambda = new AWS.Lambda();

export async function listfunctions(event, context: LambdaExecutionContext, callback): Promise  {
  const functions  = [];
  let   nextMarker = null;

  do {
    const response: AWS.Lambda.ListFunctionsResponse = await lambda.listFunctions({Marker: nextMarker}).promise();

    functions.push(...response.Functions);
    nextMarker = response.NextMarker;
  } while (nextMarker);

  callback(null, {
    statusCode: 200,
    headers:    {},
    body:       JSON.stringify({functions}),
  });
};

を追加します。 AWS の API によくあるNextMarkerでの判断を含んだ非同期 N 回ループがこんなに簡単に書けちゃいます。最高。

他のファイルも微変更が必要なのですが、後述の repo に全部あげておくのでそちらを参照してください。

再度デプロイして…

$ node_modules/.bin/serverless deploy    

実行してみます。

$ curl https://********.execute-api.us-east-1.amazonaws.com/dev/listfunctions | jq

うまくいけばデプロイされたこれ自体を含む Lambda Function の一覧が取れるはずですね!

ここまでやったのがこちら

どうぞご笑覧ください。その後、色々を見直してこの記事の内容と結構違いがあったりします(index.jsを置かないようにした、とか)ので、ご注意を。

元記事はこちら

Serverless + Webpack + TypeScript + tslint メモ