NodeでServerless Frameworkの開発環境を作る(ts版)

2020.10.25
NodeでServerless Frameworkの開発環境を作る(ts版)

Node.js(TypeScript版)でServerless Frameworkの環境を作っていきます。

検証した環境

1 yarn 1.22.4
2 serverless 2.8.0

前準備

プロジェクトの初期化を行うため serverless コマンドをグローバルで使えるようにインストールします

# システム全体で使えるようにする
$ npm i -g serverless

# インストール出来た事の確認
$ serverless -v
Framework Core: 2.8.0
Plugin: 4.1.1
SDK: 2.3.2
Components: 3.2.3


serverless コマンドはインストールした時点で sls という短縮コマンドが使えるようになります

# インストール時点で短縮コマンドが使える
$ sls -v
Framework Core: 2.8.0
Plugin: 4.1.1
SDK: 2.3.2
Components: 3.2.3

プロジェクトを初期化

ここではNode.jsのTypeScript版で初期化します。
私は「serverless-ts-sample」というフォルダにプロジェクトを作っていきます

$ cd <プロジェクトフォルダ>

# プロジェクトフォルダでServerless Frameworkの雛形を作るためのコマンドを実行
$ sls create -t aws-nodejs-typescript

このコマンドを実行するとフォルダ直下にファイルやフォルダがいくつか作られます

serverless-ts-sample/
  .gitignore
  .vscode/
  handler.ts
  package.json
  README.md
  serverless.ts
  tsconfig.json
  webpack.config.js

プロジェクト初期化時のコマンドについて(余談)

実行フォルダ直下にファイル群を作るか、フォルダの中にまとめた状態で作るか、で実行する $ sls create コマンドが変わってきます。( -p <プロジェクト名> を付与するかどうかの違い)


例えば「hoge/」というフォルダで

$ sls create -t aws-nodejs-typescript -p fuga

と実行すると

hoge/
  fuga/
    handler.ts
    package.json
    ・・・

となり、


$ sls create -t aws-nodejs-typescript

と実行すると

hoge/
  handler.ts
  package.json
  ・・・

となります。

プロジェクトに serverless を導入

この時点だとプロジェクト内に serverless のパッケージが存在していません

そのため以下のコマンドで追加します

# プロジェクトにserverlessをインストール
$ yarn add serverless
・・・
✨  Done in 10.68s.

# プロジェクト内のserverlessのバージョン確認
$ npx sls -v
Framework Core: 2.8.0 (local)
Plugin: 4.1.1
SDK: 2.3.2
Components: 3.2.3

ここからはこのプロジェクト内にインストールした serverless を使っていきます。

serverlessが実行出来る事の確認

$ sls create をした時点で、エンドポイントを叩くとサンプルのJSONが返ってくる参考用の hello メソッドが用意されています。(細かい処理の流れの解説は省略)

handler.ts
import { APIGatewayProxyHandler } from 'aws-lambda'
import 'source-map-support/register'

export const hello: APIGatewayProxyHandler = async (event, _context) => {
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!',
        input: event,
      },
      null,
      2
    ),
  }
}

まずそのメソッドが使えるかの確認をしてみます。
メソッドを試すには $ sls invoke コマンドを使用します。

# ローカルのsls invokeを使い hello メソッドを試す
$ npx sls invoke local --function hello

or

# --function は -f と省略する事が可能
$ npx sls invoke local -f hello

実行すると webpack のバンドルが行われ、200のステータスコードが返ってくれば成功です!

$ npx sls invoke local -f hello
Serverless: Bundling with Webpack...
Time: 79ms
Built at: 10/17/2020 11:29:27 AM
     Asset      Size   Chunks             Chunk Names
handler.js  6.29 KiB  handler  [emitted]  handler
Entrypoint handler = handler.js
[./handler.ts] 316 bytes {handler} [built]
[source-map-support/register] external "source-map-support/register" 42 bytes {handler} [built]
{
    "statusCode": 200,
    "body": "{\n  \"message\": \"Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!\",\n  \"input\": \"\"\n}"
}

serverless invoke について

Serverless Frameworkで作成するメソッドは、

  • エンドポイントを叩く
  • cronで決めた時間に実行する

等、何かしらトリガーがあるはず、ですが、$ sls invoke はそれらのメソッドをすぐに実行し試す事が出来ます。



もちろんローカルだけではなくデプロイしたものも実行出来ます。
デプロイしたものは $ sls invoke hello とすればOK!

$ npx sls invoke --help
Plugin: Invoke
invoke ........................ Invoke a deployed function
invoke local .................. Invoke function locally
    --function / -f (required) ......... The function name
    --stage / -s ....................... Stage of the service
    --region / -r ...................... Region of the service
    --qualifier / -q ................... Version number or alias to invoke
    --path / -p ........................ Path to JSON or YAML file holding input data
    --type / -t ........................ Type of invocation
    --log / -l ......................... Trigger logging data output
    --data / -d ........................ Input data
    --raw .............................. Flag to pass input data as a raw string
    --app .............................. Dashboard app
    --org .............................. Dashboard org
    --config / -c ...................... Path to serverless config file

Prettierを導入(お好み)

Prettierを導入してルール決めしてしまえば、コーディングに集中しやすくなるので私はこの時点でPrettierを導入してます。

$ yarn add -D prettier


私の場合1人でプロジェクトを進める時はファイル数を増やしたくなくてPrettierの設定を基本的にpackage.jsonに書いてしまいますが、Prettierの設定内容や記載場所に関しては環境や好みに合わせて読み替えて下さい。

package.json
{
  "name": "serverless-ts-sample",
  ・・・
  "prettier": {
    "singleQuote": true,
    "semi": false,
    "printWidth": 100,
    "arrowParens": "avoid"
  }
}

この時点だとPrettierを効かせたいファイルがルートのみなので以下のコマンドを実行します

$ npx prettier --write "./{*.ts,*.js}"
handler.ts 153ms
serverless.ts 10ms
webpack.config.js 35ms

serverless.ts → serverless.yml に

ここもお好みになってしまうのですが、 aws-nodejs-typescript で作っているからなのか、最近の $ sls create がそうなのかは分からないのですが、 Serverless Frameworkの設定ファイルが serverless.yml ではなく serverless.ts で作られます


「.yml」ファイルか「.ts」ファイルの差ですが、ネット上に上がっている記事が「.yml」で書かれているものがほとんどで、 かつ以前にプラグインを入れようとして「.ts」だと詰まってしまった事があり、私的に serverless.yml で書くのがおすすめです。



そのため serverless.yml を追加し、 serverless.ts を削除します。

serverless.ymlを追加

serverless.yml の最小単位として以下のようになります

serverless.yml
# 自身のサービス名を記載
service: serverless-ts-sample

# webpackでバンドルするため serverless-webpack は必須
plugins:
  - serverless-webpack

# webpack.config.jsでexternalsを設定している場合、includeModules: true が必須
# 設定しないとデプロイ後に Runtime.ImportModuleError というエラーが起きる
custom:
  webpack:
    includeModules: true

# awsを使用する事を明記
# regionは使用したいリージョンを指定
# 日本の場合、東京リージョンの ap-northeast-1 を利用する事が多いはず
# runtimeには serverless.ts に書かれていたnodejsのバージョンを記載
provider:
  name: aws
  region: ap-northeast-1
  runtime: nodejs12.x

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get

serverless.tsを削除

「serverless.ts」ファイルを削除します。

ファイルを削除しても $ sls invoke が実行出来る事を確認

$ npx sls invoke local -f hello
Serverless: Bundling with Webpack...
・・・
{
    "statusCode": 200,
    "body": "{\n  \"message\": \"Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!\",\n  \"input\": \"\"\n}"
}

処理を追加

せっかくなので処理を追加してみます


Serverless Frameworkの処理流れは大きく以下のようになります。

  • 1.serverless.yml に処理の設定を追加
    • 何をトリガーとするか?例えばエンドポイントを指定したAPIアクセスなど
    • その処理が呼ばれた時、実際の処理の呼び出し先の指定
  • 2.実際の処理を追加

serverless.yml に処理の設定を追加

処理の追加は functions の項目に行います。

サンプルとして書かれている hello がとても分かりやすい!

serverless.yml
functions:
  # 処理の名前、invokeを使って呼び出す際に使用
  hello:
    # <ファイル名>.<そのファイル内のメソッド名>
    handler: handler.hello
    events:
      - http:
          # <エンドポイント>/helloで呼び出される
          # 仮に別項目で独自ドメイン(ex: hogehoge.com)を指定しデプロイした場合、
          # http://hogehoge.com/hello のようにして呼び出す事が可能
          path: hello
          method: get


↑を例に example という項目を作成してみます

serverless.yml
functions:
・・・
  example:
    handler: sample.example
    events:
      - http:
          path: sample
          method: get

実際の処理を追加

handler: sample.example としたので「sample.ts」に example メソッドを用意します

sample.ts
import { APIGatewayProxyHandler } from 'aws-lambda'
import 'source-map-support/register'

export const example: APIGatewayProxyHandler = async (_event, _context) => {
  console.log(_context)

  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'hello Serverless Framework'
      },
      null,
      2
    ),
  }
}

console.log をしておくと $sls invoke した場合などに表示されます



ここで最終的にreturnしている値は APIGatewayProxyResult として定義されています。
statusCodebody は必須なんですね

/**
 * Works with Lambda Proxy Integration for Rest API or HTTP API integration Payload Format version 1.0
 * @see - https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
 */
export interface APIGatewayProxyResult {
    statusCode: number;
    headers?: {
        [header: string]: boolean | number | string;
    };
    multiValueHeaders?: {
        [header: string]: Array<boolean | number | string>;
    };
    body: string;
    isBase64Encoded?: boolean;
}


追加したメソッドが使用出来るか試します

$ npx sls invoke local -f example
・・・
{
  invokeid: 'id',
  logGroupName: '/aws/lambda/serverless-ts-sample-dev-example',
  logStreamName: '2015/09/22/[HEAD]13370a84ca4ed8b77c427af260',
  functionVersion: 'HEAD',
  isDefaultFunctionVersion: true,
  functionName: 'serverless-ts-sample-dev-example',
  memoryLimitInMB: '1024',
  succeed: [Function: succeed],
  fail: [Function: fail],
  done: [Function: done],
  getRemainingTimeInMillis: [Function: getRemainingTimeInMillis]
}
{
    "statusCode": 200,
    "body": "{\n  \"message\": \"hello Serverless Framework\"\n}"
}

無事、200が返ってきました!

AWSにデプロイ&削除

最後にデプロイします。



Serverless Frameworkの何がすごい!てここまでもすごいけど、
デプロイもコマンド1個でデプロイ出来るんです!


1個ですよ!
$ npx sls deploy すれば、APIGatewayやLambda、S3等色んなところに勝手にデプロイしてくれます!
なんだよ、神かよ、神なのかよ、、、

AWSにデプロイ

$sls deploy は -v--verbose )を付けると実行している内容の詳細が表示されるのでおすすめです。

$ npx sls deploy -v
{
  "webpackConfig": "webpack.config.js",
  "includeModules": false,
  "packager": "npm",
  "packagerOptions": {},
  "keepOutputDirectory": false
}
・・・
・・・
CloudFormation - UPDATE_IN_PROGRESS - AWS::CloudFormation::Stack - serverless-ts-sample-dev
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::RestApi - ApiGatewayRestApi
CloudFormation - CREATE_IN_PROGRESS - AWS::Logs::LogGroup - HelloLogGroup
CloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::Logs::LogGroup - ExampleLogGroup
CloudFormation - CREATE_IN_PROGRESS - AWS::ApiGateway::RestApi - ApiGatewayRestApi
CloudFormation - CREATE_COMPLETE - AWS::ApiGateway::RestApi - ApiGatewayRestApi
CloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecution
・・・
CloudFormation - CREATE_COMPLETE - AWS::ApiGateway::Resource - ApiGatewayResourceHello
CloudFormation - CREATE_COMPLETE - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Function - HelloLambdaFunction
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Function - ExampleLambdaFunction
・・・
・・・
Serverless: Stack update finished...
Service Information
service: serverless-ts-sample
stage: dev
region: ap-northeast-1
stack: serverless-ts-sample-dev
resources: 17
api keys:
  None
endpoints:
  GET - https://fghbzaelkd.execute-api.ap-northeast-1.amazonaws.com/dev/hello
  GET - https://fghbzaelkd.execute-api.ap-northeast-1.amazonaws.com/dev/sample
functions:
  hello: serverless-ts-sample-dev-hello
  example: serverless-ts-sample-dev-example
layers:
  None

無事デプロイ出来ました!

endpoints: に試しで追加した sample もあるのでアクセスしてみます。



まずchromeでエンドポイントにアクセス



続いて $sls invoke

$ npx sls invoke -f example
{
    "statusCode": 200,
    "body": "{\n  \"message\": \"hello Serverless Framework\"\n}"
}

OKですね!

AWSから削除

削除もコマンド一発です!

ゴッソリきれいにしてくれます、何から何まですごいなぁ、ほんと。


こちらも同じく -v を付けておくと何をやっているかが見られて安心感があります

$ npx sls remove -v
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
CloudFormation - DELETE_IN_PROGRESS - AWS::CloudFormation::Stack - serverless-ts-sample-dev
・・・
Serverless: Stack removal finished...

これで削除も完了です!

おすすめ