LoginSignup
3
1

More than 1 year has passed since last update.

Serverless Framework × TypeScript のローカル開発環境構築メモ(Serverless Offline編)

Last updated at Posted at 2022-01-13

Serverless Framework × TypeScript のローカル開発環境構築メモ(Serverless Offline編)[今回]
Serverless Framework × TypeScript のローカル開発環境構築メモ(DynamoDB Local 編)
Serverless Framework × TypeScript のローカル開発環境構築メモ(S3 Local編)

Serverless Frameworkとは、サーバーレスアーキテクチャでAPIを作る際に面倒なインフラ構築をコマンド一発でやってくれる優れもののフレームワークです!今回はこのServerless Frameworkをローカルで動かせるプラグインServerless Offineを使ってローカル開発できるようにしていきましょう。

Severless Frameworkの環境構築

まず、serverless frameworkをインストールし、typescriptテンプレートを使ってビルドします。詳しくはリンク先を参照ください。

$ npm install -g serverless
$ serverless create --template aws-nodejs-typescript --path api

この時のディレクトリ構成は次のようになっています。

api
├── README.md
├── package.json
├── serverless.ts
├── src
│   ├── functions
│   │   ├── hello
│   │   │   ├── handler.ts
│   │   │   ├── index.ts
│   │   │   ├── mock.json
│   │   │   └── schema.ts
│   │   └── index.ts
│   └── libs
│       ├── apiGateway.ts
│       ├── handlerResolver.ts
│       └── lambda.ts
├── tsconfig.json
└── tsconfig.paths.json

serverless.tsがテンプレートファイルとなっており、このテンプレートではデフォルトでmiddyjson-schema-to-tsといったプラグインが入っています。そのため、関数を書く際は、内部のビジネスロジックとAPIのインターフェイスにのみ注力すれば良くなります。ただし、application/json以外のContent Typeの場合は修正が必要になります。

functionsディレクトリがLambda関数のソースコードを書くディレクトリです。helloデレクトリを見ると、index.tsでLambda関数の設定を書き、handler.tsで実際のハンドラーを書く形になっています。schema.tsではhttpリクエストのスキーマを定義しており、json-schema-to-tsでtype化してハンドラの型定義に使っています。実際に次のコマンドで関数をローカルで実行できます。

$ npx sls invoke local -f hello --path ./src/functions/hello/mock.json

Serverless Offlineの導入

前述のinvoke localは関数を実行するだけだったので、フロントエンドとの結合をテストするなどでAPIを立てる必要がある場合にはプラグインを導入する必要があります。そのためにローカルAPIを立ててくれるプラグインがServerless Offlineです。公式に従ってインストールしていきましょう。まず、以下のコマンドを実行します。

$ npm install -D serverless-offline

あとは、serverless.ts内のpluginsの最後に"serverless-offline"を挿入するだけです。

serverless.ts
  plugins: ["serverless-esbuild", "serverless-offline"],

ここまでできたら、次のコマンドでローカルAPIを立ててみましょう。

$ npx sls offline start

次のようなメッセージが出ていれば、localhost:3000にパス/dev/helloのAPIが立っていることになります。

   ┌─────────────────────────────────────────────────────────────────────────┐
   │                                                                         │
   │   POST | http://localhost:3000/dev/hello                                │
   │   POST | http://localhost:3000/2015-03-31/functions/hello/invocations   │
   │                                                                         │
   └─────────────────────────────────────────────────────────────────────────┘

offline: [HTTP] server ready: http://localhost:3000 🚀
offline: 
offline: Enter "rp" to replay the last request
^Coffline: Got SIGINT signal. Offline Halting...
offline: Halting offline server

次のコマンドで接続確認してみましょう。nameは各自の名前に変えてくださいね。

$ curl -X POST -H "Content-Type: application/json" -d "{\"name\": \"Utan\"}" localhost:3000/dev/hello

何もコードをいじってなければ次のレスポンスが返ってくるはずです。もちろんnameは各自の名前で。

{"message":"Hello Utan, welcome to the exciting Serverless world!","event":{"body":{"name":"Utan"},"headers":{"Host":"localhost:3000","User-Agent":"curl/7.77.0","Accept":"*/*","Content-Type":"application/json","Content-Length":"16"},"httpMethod":"POST","isBase64Encoded":false,"multiValueHeaders":{"Host":["localhost:3000"],"User-Agent":["curl/7.77.0"],"Accept":["*/*"],"Content-Type":["application/json"],"Content-Length":["16"]},"multiValueQueryStringParameters":null,"path":"/hello","pathParameters":null,"queryStringParameters":null,"requestContext":{"accountId":"offlineContext_accountId","apiId":"offlineContext_apiId","authorizer":{"principalId":"offlineContext_authorizer_principalId"},"domainName":"offlineContext_domainName","domainPrefix":"offlineContext_domainPrefix","extendedRequestId":"ckydl1ztx0003r28z3ftnejyj","httpMethod":"POST","identity":{"accessKey":null,"accountId":"offlineContext_accountId","apiKey":"offlineContext_apiKey","apiKeyId":"offlineContext_apiKeyId","caller":"offlineContext_caller","cognitoAuthenticationProvider":"offlineContext_cognitoAuthenticationProvider","cognitoAuthenticationType":"offlineContext_cognitoAuthenticationType","cognitoIdentityId":"offlineContext_cognitoIdentityId","cognitoIdentityPoolId":"offlineContext_cognitoIdentityPoolId","principalOrgId":null,"sourceIp":"127.0.0.1","user":"offlineContext_user","userAgent":"curl/7.77.0","userArn":"offlineContext_userArn"},"path":"/hello","protocol":"HTTP/1.1","requestId":"ckydl1ztx0004r28zdrcjbjdq","requestTime":"14/Jan/2022:08:08:26 +0900","requestTimeEpoch":1642115306660,"resourceId":"offlineContext_resourceId","resourcePath":"/dev/hello","stage":"dev"},"resource":"/hello","rawBody":"{\"name\": \"Utan\"}"}}

Lambda関数を足してみる

以上で、基本的なローカルAPI開発環境は整いました!あとはfunctionsディレクトリにどんどん関数を足していくだけですね。そのやり方をメモしておきます。

関数を足す際には、まず次のようにfunctionsディレクトリに新しいディレクトリを足します。今回はhogeディレクトリを足しました。

api/src/functions
├── hello
│   ├── handler.ts
│   ├── index.ts
│   ├── mock.json
│   └── schema.ts
├── hoge
│   ├── handler.ts
│   ├── index.ts
│   ├── mock.json
│   └── schema.ts
└── index.ts

次に、hoge/index.ts, handler.tsを適当に書き換えます。今回は次のように書き換えました。

hoge/index.ts
import { handlerPath } from '@libs/handlerResolver';

export default {
  handler: `${handlerPath(__dirname)}/handler.main`,
  events: [
    {
      http: {
        method: 'get',
        path: 'hoge',
      }
    }
  ]
}
hoge/handler.ts
import type { ValidatedEventAPIGatewayProxyEvent } from "@libs/apiGateway";
import { formatJSONResponse } from "@libs/apiGateway";
import { middyfy } from "@libs/lambda";

const handler: ValidatedEventAPIGatewayProxyEvent<{}> = async (event) => {
  return formatJSONResponse(200, {
    message: `Hoge !!`,
    event,
  });
};

export const main = middyfy(handler);

あとは、serverless.tsfunctionsにエクスポートしたhoge関数をインポートしてやるだけです。

serverless.ts
  functions: { hello, hoge },

ここまでできたらnpx sls offline startで再度ローカルAPIを立てましょう。次のようにhogeのパスが追加されているはずです。

   ┌─────────────────────────────────────────────────────────────────────────┐
   │                                                                         │
   │   POST | http://localhost:3000/dev/hello                                │
   │   POST | http://localhost:3000/2015-03-31/functions/hello/invocations   │
   │   GET  | http://localhost:3000/dev/hoge                                 │
   │   POST | http://localhost:3000/2015-03-31/functions/hoge/invocations    │
   │                                                                         │
   └─────────────────────────────────────────────────────────────────────────┘

offline: [HTTP] server ready: http://localhost:3000 🚀
offline: 
offline: Enter "rp" to replay the last request

実際にGETリクエストを送って確認してみましょう。

$ curl -X GET localhost:3000/dev/hoge

次のようなレスポンスが帰ってくるはずです。

{"message":"Hoge !!","event":{"body":null,"headers":{"Host":"localhost:3000","User-Agent":"curl/7.77.0","Accept":"*/*"},"httpMethod":"GET","isBase64Encoded":false,"multiValueHeaders":{"Host":["localhost:3000"],"User-Agent":["curl/7.77.0"],"Accept":["*/*"]},"multiValueQueryStringParameters":null,"path":"/hoge","pathParameters":null,"queryStringParameters":null,"requestContext":{"accountId":"offlineContext_accountId","apiId":"offlineContext_apiId","authorizer":{"principalId":"offlineContext_authorizer_principalId"},"domainName":"offlineContext_domainName","domainPrefix":"offlineContext_domainPrefix","extendedRequestId":"ckydluco000000w8z1th29e49","httpMethod":"GET","identity":{"accessKey":null,"accountId":"offlineContext_accountId","apiKey":"offlineContext_apiKey","apiKeyId":"offlineContext_apiKeyId","caller":"offlineContext_caller","cognitoAuthenticationProvider":"offlineContext_cognitoAuthenticationProvider","cognitoAuthenticationType":"offlineContext_cognitoAuthenticationType","cognitoIdentityId":"offlineContext_cognitoIdentityId","cognitoIdentityPoolId":"offlineContext_cognitoIdentityPoolId","principalOrgId":null,"sourceIp":"127.0.0.1","user":"offlineContext_user","userAgent":"curl/7.77.0","userArn":"offlineContext_userArn"},"path":"/hoge","protocol":"HTTP/1.1","requestId":"ckydluco000010w8zh9agf16c","requestTime":"14/Jan/2022:08:30:29 +0900","requestTimeEpoch":1642116629657,"resourceId":"offlineContext_resourceId","resourcePath":"/dev/hoge","stage":"dev"},"resource":"/hoge"}}

終わりに

ここまででServerless FrameworkでサーバーレスAPIのローカル開発環境の基本的な部分が整ったかと思います。あとはどんどん関数を足してリッチなAPIにしてくだけですね!!次回はDynamoDBのローカル開発環境をServerlessで立てていきます!ぜひ最高のエンジニアライフを!!

3
1
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
3
1