"Lambda API"と"Serverless Framework"を使ってLambda上にサーバーレスAPIを構築する

## 前置き

APIの構築というジャンルでは[AWS Lambda](https://aws.amazon.com/jp/lambda/) + [AWS API GateWay](https://aws.amazon.com/jp/api-gateway/)という組み合わせは鉄板ではないでしょうか。メンテナンスが不要でコストが低く、サーバ運用の悩みを抱えなくて済むので気に入っています。

ですがLambdaと出会った頃の私は「Lambdaにどんなソースコードを書いたらいいのかわからない」となっていました。
そして少しの時が経ち、最近の私はAPIの処理を[Lambda API](https://github.com/jeremydaly/lambda-api)で実装し、それを[Serverless Framework](https://www.serverless.com/)によって展開するのがかなりイケてるのではと思うようになりました。この記事ではこの構成について解説します。

名称がとてもわかりにくいのですが、Lambda APIもServerless Frameworkもフレームワーク名(=固有名詞)です。私は最初、仕組み(やり方)と勘違いして理解が遅くなりました。このことを念頭に読み進めていくと理解がスムーズになるかと思います。

なお、Lambda APIとServerless Frameworkは直接関係するものではありません。ですが両者を同時に導入するのが最も開発体験 (DX=DeveloperExperience) が高くなると思うので、同時に紹介することにしました。

### この記事で紹介する構成

![この記事で解説する構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231426/513d510c-f0e4-9fad-b115-a4c1a5e3dd14.png)

### 勘違いされそうな本記事の要約

- 誤: LambdaにサーバーレスなAPIを作る方法を解説
  - 完全な誤りではありませんが、やりたいことは違うんです...。

### 実際の本記事の要約

- 正: [Lambda API](https://github.com/jeremydaly/lambda-api)(Lambda用のフレームワーク)と[Serverless Framework](https://www.serverless.com/)(AWSの構成管理に関するフレームワーク)によってAPIサーバを構築する方法を解説

### この記事で詳しく解説しないこと

- AWSの初歩的なこと (Lambdaとは、API Gatewayとは、IAMとは、etc)
- JavaScriptやNodeのこと

## Lambda API

### 言葉の定義

この記事で単にLambdaと記載した場合は[AWS Lambda](https://aws.amazon.com/jp/lambda/)(AWSが提供するサービス)を指します。
Lambda APIと記載した場合は[Lambda API ](https://github.com/jeremydaly/lambda-api)(フレームワーク)を指します。

### なぜLambda API？

Lambdaの用途は縛られることがなく、ソースコードをかなり自由に書くことができます。これは嬉しい側面もありますが、一方でAPI構築では「どのようなソースコードが適切かを検討しないといけない」「よくあるAPI用の処理を自分で実装したくない」といった悩みが発生します。

そう、Lambda用のフレームワークが欲しくなるのです。

私はRuby on RailsやLaravelといったMVCフレームワークに慣れ親しんでいましたが、それらは要求されるアーキテクチャが異なるためLambdaで動かすことができません。ExpressやFastifyといったNode製のフレームワークなら動くのかもしれませんが、必要以上に重たい印象があります。

そんな時に導入をオススメしたいのがLambda APIです。

### Lambda APIとは

#### プロフィール

- npmレジストリ: [lambda-api - npm](https://www.npmjs.com/package/lambda-api)
- GitHub リポジトリ: [ jeremydaly / lambda-api](https://github.com/jeremydaly/lambda-api)
- GitHub Stars: 1.2k 
- 作成開始: 2017年頃 (Gitのfirst commitから取得)
- 最終更新: 2023-03-14
- 最新バージョン: v1.0.1
- ライセンス: MIT
- 作者(メインコントリビュータ): [jeremydaly (Jeremy Daly) - GitHub](https://github.com/jeremydaly)
- 開発言語: JavaScript

※ いずれも2023-03-30時点

#### LambdaAPIの簡易解説

READMEに記載されているフレームワークの趣旨をざっくり書くと「Node製のWebフレームワーク (Expressなど) は素晴らしいけれどサーバレスのLambdaには向いていていない。そのため他のパッケージに依存しない軽量なフレームワークを作成した」とのことでした。
また、[Express](https://expressjs.com/ja/)や[Fastify](https://www.fastify.io/)と似た構文を採用しているとのことで、それらの前提知識があればスムーズにコーディングできます。

詳細は[README](https://github.com/jeremydaly/lambda-api#readme)を参照してください。

#### なぜ私がLambda APIをオススメするのか

Lambda上にAPIサーバを構築しようとした時、APIのコアにあたる処理は似たようなものに落ち着くので、自分で実装するのは無駄(車輪の再発明)と感じます。労力削減のためにフレームワークやライブラリに頼りたくなります。
また、Lambdaの特性やメリットを考えると、高機能なフレームワークは向かないと感じます。

こう考えていた時にLambda APIと出会いました。

欲しい機能が"必要"かつ"十分"に揃っているフレームワークを探したところ、自分が考える状態に最も近かったのがLambda APIでした。
Lambda APIは作りたいロジックの構築に注力させてくれました。構文がExpressと似ていて取り掛かりやすかったです。そして親切でシンプルなREADMEが揃っていました。

#### Lambda APIの採用にあたって心配な点

小さめなプロジェクトのため、長期的にメンテナンスが続いていくことに対しては少し不安があります。
ですがコード量が少なく、MITライセンスなので、いざとなったら自身でパッチを当てればよいという感覚でよいと考えています。

#### Lambda API以外の選択肢

同じようなフレームワークやライブラリは他にも存在します。私はLambda APIが好みでしたが、他を採用する方がいいケースもあるかもしれません。

Lambda APIとの比較検討で調べたものを名前だけ紹介します。リンク先は最もわかりやすそうなところにしました。そのため統一感はありません。

- [Middy](https://middy.js.org/) (Middyの公式ドキュメント)
- [Serverless Express](https://github.com/vendia/serverless-express) (GitHub)
- [Fastify - Serverless](https://www.fastify.io/docs/latest/Guides/Serverless/) (Fastify内のServerlessのドキュメント)

## Serverless Frameworkとは

一旦Lambda APIのことを横に置き、続いてServerless Frameworkの紹介に移ります。

Lambda APIのことを知らなくてもServerless Frameworkのことを知っている人は多いのではないでしょうか。
ここでは表面的なことしか解説しないので、既に知っている方はこの節を読み飛ばしてください。

### プロフィール

- 公式HP: [Serverless](https://www.serverless.com/)
- GitHub リポジトリ: [serverless / serverless](https://github.com/serverless/serverless)
- GitHub Stars: 44.4k 
- 作成開始: 2015年頃 (Gitのfirst commitから取得)
- 最終更新: 2023-03-24
- 最新バージョン: v3.29.0
- ライセンス: MIT
- 作者: Serverless, Inc.
- 開発言語: Node.js

※ いずれも2023-03-30時点

### Serverless Frameworkの簡易解説

Serverless Frameworkとはサーバレス環境 (サーバを意識する必要がない環境) やその周辺環境を構成管理 (コードをデプロイしたり、リソースを管理したり) するためのフレームワークです。
Serverless FrameworkはAzureやGCPなどにも対応しているようですが、AWSで使うことが一般的です。

### なぜ私がServerless Frameworkをオススメするのか

LambdaやAPI Gatewayを利用する際、様々な設定が必要となります。
これは従来のサーバ構築に比べれば楽ですが、それでも少し大変です。限りある時間をAPI本体の構築に割きたいです。
Serverless Frameworkなら設定ファイルを用意するだけでこれを実現してくれます。

Serverless Frameworkは広く利用されています。Lambdaの採用を決めた時点で迷うことなくServerless Frameworkの採用も決めるケースも多いのではないでしょうか。

### なぜLambda APIと同時にserverless frameworkもオススメするのか

両者は直接関連するものではありません。
ですがLambda APIを使用した開発の際にはServerless Frameworkの導入も必須と考えています。そのため、この記事では両者を同時に解説することにしました。
ちなみに[Lambda APIの作者の導入解説](https://www.jeremydaly.com/build-serverless-api-serverless-aws-lambda-lambda-api/)でもServerless Frameworkを前提とした手順を紹介していました。

## 実際の導入手順

ここまでで解説してきたことを実際に構築してみたいと思います。

ソースコードの完成形はGitHubに上げてあります。

GitHub [ljourm/sample-lambda-api-serverless](https://github.com/ljourm/sample-lambda-api-serverless)
コミットごとに参照すると読みやすいかもしれません ([各コミットへのリンク](https://github.com/ljourm/sample-lambda-api-serverless/commits/main))

### Serverless Frameworkの導入

- 対応するGitHubのコミット
    - [feat: serverlessの導入](https://github.com/ljourm/sample-lambda-api-serverless/commit/a5d16df04c7a7c5b4a442ee434870cf33a3a7e19)
    - [feat: serverlessが最低限動作するように整備](https://github.com/ljourm/sample-lambda-api-serverless/commit/9c4b56924556cde753f258cca89c2660799c06f6)

まずServerless Frameworkを導入します。
グローバルで使用可能にする記事が多いですが、私はyarnでpackage.jsonに導入する方が好きなので、こちらの書き方で進めます。

```sh
$ yarn add -D serverless

# 以下を紹介している記事が多いが、今回はyarnでpackage.jsonに追加する方法で記載
npm install -g serverless
```

次にLambda関数用のソースコードを作成します。この時点ではまだLambda APIは未使用です。

```js:handler.js
exports.router = async (event, context, callback) => {
  return {
    statusCode: 200,
    headers: {},
    body: JSON.stringify({ message: "hello world" }),
  };
};
```

そしてServerless Frameworkの設定を追加します。初見でもなんとなく読めるのではないでしょうか。

```yml:serverles.yml
service: sample-lambda-api

provider:
  name: aws
  runtime: nodejs18.x
  region: ap-northeast-1

functions:
  hello: # 関数名をhelloにする
    handler: handler.router # handler.jsのrouterメソッドを呼び出す
    events:
      - httpApi: # API Gatewayにアクセスしてきた全てをLambdaに流す
          method: "*"
          path: "*"
```

ここまで作成したらデプロイコマンドを実行します。
これによってLambdaにhandler.jsがデプロイされ、さらにLambdaに繋げるためのAPI Gatewayも同時に生成されます。

```sh
$ yarn serverless deploy
```

API GatewayのURLにアクセスするとレスポンスの取得ができるようになります。

![APIGatewayのURL取得先.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231426/b38c9f13-3dfc-6366-3785-839bfc262474.png)



### Lambda APIの導入

- 対応するGitHubのコミット
    - [feat: lambda-apiの導入](https://github.com/ljourm/sample-lambda-api-serverless/commit/7fa517f941adcdbfeddde68327856af0f7b8f44d)
    - [feat: lambba-apiのpeerDependenciesの導入](https://github.com/ljourm/sample-lambda-api-serverless/commit/bfd100b96a90415995ea55cb1f3c665cdf23b49e)
    - [feat: lambda-apiを最低限で導入](https://github.com/ljourm/sample-lambda-api-serverless/commit/210fd6992aa838f745512e5c36e1538361079f90)

続いてLambda APIを導入します。

```sh
# lambda-apiの導入
$ yarn add lambda-api

# lambda-apiの依存パッケージの導入
$ yarn add -D @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
```

次に先ほど作成したhandler.jsをLambda APIを使用した状態に書き換えます。
ここではLambda APIのサンプルとほとんど同じコードを書いてみます。Expressを知っている方にとっては馴染みのある文法になっていると思います。

```js:handler.js
// 先ほどのコードは全て削除し、以下を記述

const api = require("lambda-api")();

api.get("/status", async (req, res) => {
  return { status: "ok" };
});

exports.router = async (event, context, callback) => {
  return await api.run(event, context);
};
```

ここまで作成したら再度デプロイコマンドを実行します。
これによって先ほどデプロイしたLambdaのhandler.jsが更新されます。ちなみにAPI Gatewayは同じなため変更されません。

```sh
$ yarn serverless deploy
```

再度API GatewayのURLにアクセスすると変更されたレスポンスの取得ができるようになります。

![APIGatewayとの通信結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231426/668a02b4-8525-3fc9-ce1c-d1ed87765fdc.png)



### Serverless Offlineの導入

- 対応するGitHubのコミット
    - [feat: serverless-offlineの導入](https://github.com/ljourm/sample-lambda-api-serverless/commit/93a738f23b3e39f1796ac0ef023af6f7697edba3)

ここまででAWS環境にLambda APIを使用したJavaScriptをデプロイすることが可能になりました。ですが、今の段階では作成したプログラムの動作確認をローカル環境で行うことができません。
[invoke local](https://www.serverless.com/framework/docs/providers/aws/cli-reference/invoke#provided-lifecycle-events)を使うと少し解決できますが、使いやすいものではない印象です。

そこでServerless Flameworkのプラグイン[Serverless Offline](https://www.serverless.com/plugins/serverless-offline)を導入して、オフライン (ローカル環境) で動作可能にします。

![ServerlessFrameworkOfflineを使った構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231426/f1b8af11-7eb5-5292-e09d-57514137c2a2.png)

以下によってプラグインを導入してください。

```sh
$ yarn add -D serverless-offline
```

```yml:serverless.yml
plugins:
  - serverless-offline
```

そして以下を実行します。これによってローカル環境にサーバが立ち上がります。

```sh
$ yarn serverless offline

# Docker環境の場合、アクセス元が絞られるため--hostを追加してください
$ yarn serverless offline --host 0.0.0.0
```

これで `localhost:3000` にアクセスすることができるようになりました。
毎回Lambdaにデプロイして動作確認するのは大変なので、このやり方はとても便利だと思います。

![localhostとの通信結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231426/309d8278-1898-e11d-5d9d-704a08167292.png)


## 最後に

この記事ではLambda APIの使い方について細かく解説していませんが、[README](https://github.com/jeremydaly/lambda-api#readme)はサンプルも添えて記載されているので、英語が苦手な人でも読みやすい内容ではないかと思います。

Lambda APIは今のところ、それほど広く知られてはいないようです。
この記事によってLambda APIで軽量なAPIが簡単に作れることが伝わり、ユーザーが増えていくと嬉しいです。
そしてLambda APIを使う際はServerless Frameworkも同時に使ってみてください。