8
5

More than 1 year has passed since last update.

TypeScriptによるServerless Frameworkの利用

Last updated at Posted at 2021-10-05

はじめに

  • Serverless FrameworkをTypeScriptテンプレート(aws-nodejs-typescript)で利用した情報があまり見当たらなかったので、プロジェクト作成から設定内容、ファイル構成なんかについて記載します。
  • 今後利用する方の参考になればと。

プロジェクト作成

  • Serverless Frameworkをインストールした後にプロジェクトを作成します。
    • テンプレートにaws-nodejs-typescriptを利用します。
$ sls create --template aws-nodejs-typescript --path serverless-typescript-sample
  • こちらにある内容でプロジェクトが作成されます。
    • README.mdファイルに詳細が記載されているので確認しておくと良いです。

環境の整理

静的コード解析ツールの追加

  • プロジェクト作成後の状態だとeslintなどのコード解析ツールが入っていないので、適宜追加します。
    • 細かい設定内容はお好みで設定して頂ければと。
$ yarn add -D eslint prettier eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser

  • .eslintrc.jsと.prettierrc.jsファイルを追加して設定します。

middyのバージョンが古いので更新する

  • slsで生成されるコードはmiddyパッケージのバージョンが古いのでアップデートしておきます。
$ yarn upgrade @middy/core --latest
$ yarn upgrade @middy/http-json-body-parser --latest

型定義の修正

eslintの実行

  • ここでeslintを実行するとフォーマットエラーなどでエラーがいくつか出力されるので、 --fixで適宜修正しておきます。
$ eslint "{src,test}/**/*.ts"
  • 型の指定でいくつかworningが出るので適宜修正します。
  • 現時点で出たものは以下になります。

src/libs/handlerResolver.ts

  • handlerPathの戻り値の型が指定されていないので、stringを指定するようにします。
handlerResolver.ts
export const handlerPath = (context: string): string => {
  return `${context.split(process.cwd())[1].substring(1).replace(/\\/g, '/')}`
}

src/libs/apiGateway.ts

  • formatJSONResponseの戻り値の型が指定されていないので追加しておきます。
apiGateway.ts
type JSONResponse = {
  statusCode: number
  body: string
}

export const formatJSONResponse = (
  response: Record<string, unknown>,
): JSONResponse => {
  return {
    statusCode: 200,
    body: JSON.stringify(response),
  }
}

src/libs/lambda.ts

  • middyfyの引数と戻り値の型が指定されていないので、追加しておきます。
lambda.ts
import type { Handler } from 'aws-lambda'

export const middyfy = (handler: Handler): middy.MiddyfiedHandler => {
  return middy(handler).use(middyJsonBodyParser())
}

型定義ファイル

ローカル実行

  • この時点で一度ローカル実行してみます。
  • 実行しやすいように以下のパッケージを事前にインストールしておきます。

serverless-offline

  • serverless-offline
  • ローカルでAPI Gateway + Lambdaをエミュレーションしてくれます。
$ yarn add -D serverless-offline
  • serverless.tsにプラグインとして追加します。
serverless.ts
plugins: ['serverless-webpack', 'serverless-offline'],

fork-ts-checker-webpack-plugin

  • fork-ts-checker-webpack-plugin
  • webpack実行時にESLintで型チェックをしてくれます。
    • serverless-offline実行時にあわせて型のチェックも出来るようになります。
yarn add -D fork-ts-checker-webpack-plugin
  • webpack.config.jsにプラグインとして追加します。
  • require定義の追加
webpack.config.js
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
  • plugins定義の追加
webpack.config.js
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      eslint: {
        files: './src/**/*.{ts,tsx,js,jsx}',
      },
    }),
  ],

実行

  • serverless-offlineを利用してプロセスを起動します。
$ sls offline
  • curlなどでリクエストします。
curl --request POST \
  --url http://localhost:3000/dev/hello \
  --header 'Content-Type: application/json' \
  --data '{
    "name": "hoge"
}'
  • 正しく処理されればOKです。

ファイル構成について

  • プロジェクト生成後のファイル構成についての詳細はこちらに記載されています。
  • 基本的にはfunctions/に必要なLambda-functionの処理を追加してserverless.tsに設定を追加してゆく感じになります。
  • 構成についておおまかですが、下記に記載します。

src/libs/lambda.ts

  • middlewareパッケージとしてMiddyを利用する構成になっています。
    • メインの処理を行なう前、後に共通して実行したい処理がある場合にはこちらを利用して追加してゆく感じになります。
    • デフォルトではhttp-json-body-parserを利用しています。
      • Content-Typeにapplication/jsonを指定してリクエストすることで、リクエストボディに指定したjsonをパースしてくれます。
    • こちらによく利用しそうな処理が用意されているので、それを利用するか、独自に実装することも可能です。
    • アプリケーション全体として必要な初期化処理、DBの接続処理やエラー処理などを実装するのが良いかもしれません。

src/libs/apiGateway.ts

  • httpリクエストとレスポンスの型の定義などがされています。
  • リクエストボディのjsonの型定義にはjson-schema-to-tsというパッケージが利用されていて、JSON schemaで定義したものをそのままバリデーションなどの型定義として利用出来るようになっています。
    • src/functions/hello/schema.tsでリクエストボディの型が定義されています。

src/functions/handler.ts

  • メインの処理になります。
  • デフォルトであるhelloはこちらのHandler型が定義されていて、src/functions/hello/schema.tsで定義されたデータがevent.bodyに入ってくるようになっています。
    • eventで渡されるデータはbody以外はAPIGatewayProxyEventの定義にある通りになります。

その他

リクエストデータの変更

  • リクエストボディの定義を変更する場合にはsrc/functions/hello/schema.tsファイルの定義を変更します。
  • ageという項目を必須として追加したい場合なこのような感じになります。
schema.ts
export default {
  type: 'object',
  properties: {
    name: { type: 'string' },
    age: { type: 'number' },
  },
  required: ['name', 'age'],
} as const

環境ごとの設定

  • TypeScript特有ではありませんが、デプロイ環境ごとに環境変数を設定する方法です。
  • serverless.tsに設定を追加します。
  • デプロイ時のstageオプションで環境を取得するように設定して、環境ごとの設定ファイルを追加します。
serverless.ts
  provider: {
    stage: '${opt:stage, self:custom.defaultStage}',
  • 設定ファイルの追加
env/
  dev.yml
  prod.yml
  stg.yml
  • ファイルの中身
dev.yml
ENV: 'development'
  • custom設定にdefaultStageとenvironmentを追加
    • ./env/**.ymlファイルを追加してそこから値を読み込むようにする
serverless.ts
  custom: {
    defaultStage: 'dev',
    environment: {
      dev: '${file(./env/dev.yml)}',
      stg: '${file(./env/stg.yml)}',
      prod: '${file(./env/prod.yml)}',
    },
  • environmentの設定で、環境変数を設定する
serverless.ts
    environment: {
      AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
      ENV: '${self:custom.environment.${self:provider.stage}.ENV}',
    },

AWSリソースの設定

  • serverless.tsに設定を追加します。
  • iamロール
    • providerのiamRoleStatementsに設定を追加
    • Fn::Join などの処理も追加することが出来ます
    • s3の特定のバケットへの操作を許可するように設定する例になります。
serverless.ts
    iamRoleStatements: [
      {
        Effect: 'Allow',
        Action: ['s3:*'],
        Resource: [
          {
            'Fn::Join': [
              '',
              [
                'arn:aws:s3:::',
                '${self:custom.environment.${self:provider.stage}.BUCKET_NAME}',
                '/*',
              ],
            ],
          },
          {
            'Fn::Join': [
              '',
              [
                'arn:aws:s3:::',
                '${self:custom.environment.${self:provider.stage}.BUCKET_NAME}',
              ],
            ],
          },
        ],
      }
    ]
  • リソース
    • resourcesに作成するAWSリソースを追加します。
    • s3バケット作成の例になります。
serverless.ts
  resources: {
    Resources: {
      Bucket: {
        Type: 'AWS::S3::Bucket',
        Properties: {
          BucketName:
            '${self:custom.environment.${self:provider.stage}.BUCKET_NAME}',
          VersioningConfiguration: {
            Status: 'Enabled',
          },
        },
      },
    },

まとめ

  • js(node)で実装するのと大きな違いはありませんが、いくつか外部のパッケージを利用する構成になっているので、そこだけ気をつければ問題は無いと思います。
  • aws-sdkなど外部のパッケージを利用して実装してゆくと型定義をそれぞれ行なう必要があり、若干の面倒くささはありますが、jsだけで実装しているとオブジェクトの中身も良く分からずに利用してしまっていたりするので、安全に実行できますしメンテナンス性もあがるかな、と感じます。
  • 上記の内容で作成したサンプルをこちらにアップしてあります。
8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5