最近 Hono で外部連携用の AWS Lambda 書いています

## AWS Lambda のハンドラは辛い

こんにちは！AWS Lambda初心者です！

[AWS Lambda](https://aws.amazon.com/jp/lambda/) と言えば、サーバレスかつ非常に安い価格で無限にスケールアウトできる処理を書けることで有名ですが[^1]、 [2022年04月の Function URL アップデート](https://aws.amazon.com/jp/blogs/aws/announcing-aws-lambda-function-urls-built-in-https-endpoints-for-single-function-microservices/) でAPI Gatewayなど用いなくてもラムダ単体かつ無料でHTTPリクエストを処理できるURLを持てるようになり、かなりの神サービスになりました。

[^1]: 正直AWS内での広範な扱いを見ていると、AWSの便利なマクロ拡張関数といったほうが適切な気はします

弊社でも [Function URL](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-urls.html) でラムダによる外部連携用のサーバを立てておき、ちょっとしたHTTPのリクエストを受け取って、他所のサーバにデータ連携したり、S3にリクエスト中のデータを保存したりする用途に積極的に使っています。

さて、そんな神サービスである AWS Lambda 君ですが、いざ実際にJavaScriptでHTTPサーバを書こうとすると……　ちょっと辛い！

```js
// https://lambda-function-url/echo にアクセスすると "Hello" を返すサーバの例

exports.handler = async (event) => {
    if (event.httpMethod === "GET" && event.path === "/echo") {
        return {
            statusCode: 200,
            body: "Hello"
        };
    }
    
    return {
        statusCode: 404
    };
};
```

- **`event` に何が入ってくるのかイマイチピンとこない**
    - しかもAPIゲートウェイ経由か関数URL経由かによって渡されるものが違う
    - 渡されたオブジェクト内のいろんなフィールドに同じ値が入っているが、そのうちのどれを使っていいのか分からない
- `return` も**何を返せばいいのか分からない**
- この仕様でAWS Lambdaにべったり**ベンダーロックイン**されたコードを書くのが嫌だ
    - いざとなったら自前でサーバを立てたり、類似の別サービスに乗り換えたい
- 大規模なものを書くと**詰みそう**
    - [Express.js](https://expressjs.com/ja/) のようなルーターを使いたい
    - [TypeScript](https://www.typescriptlang.org/) で書きたい

というあまり良くない開発体験になりがちです。


## そこで [Hono](https://hono.dev/) !

そこで、最近は直接ラムダのハンドラを書くのではなく、 [Hono](https://hono.dev/) を使って HTTP 用のラムダを書いています。

```ts:src/index.ts
import { Hono } from 'hono'

// 純粋なHTTPサーバの定義
const app = new Hono()

// Express.js のようなルーターを書ける　
app.get('/echo', async (c) => {
  return c.text("Hello")
})

export default app
```

```ts:src/lambda.ts
import { handle } from 'hono/aws-lambda'
import app from './index.ts'

// index.ts で定義された純粋なHTTPサーバをAWS Lambda用のアダプタでラップしてハンドラとしてエクスポート
export const handler = handle(app)
```

`src/index.ts` は純粋なHTTPサーバの定義で、 `src/lambda.ts` はそのHTTPサーバを AWS Lambda 用のアダプタでラップしたものになっています。ラムダのエントリポイントとして登録するのは `src/lambda.ts` になります。

ラムダを立てずに、ローカルでHTTPサーバを実行するには `src/index.ts` を起動すればよく、以下のコマンドで起動できます。
（以降Bunを実行していますが、Node.jsでも設定を行えば基本的に実行可能です）

```bash
$ bun run --hot src/index.ts
Started server http://localhost:3000

$ curl http://localhost:3000/echo
Hello
```

逆にAWS Lambda上で上記のサーバを起動するには ESBuild などでバンドル＆zip化した `src/lambda.ts` をAWS LambdaにアップロードすればOKです。

```bash
# ESBuild でバンドル (外部のライブラリを参照していてもうまい具合に1ファイルにバンドルされる)
$ bun run esbuild --bundle \
    --outfile=./dist/index.js \
    --platform=node --target=node20 \
    ./src/lambda.ts

# zip化
$ zip -j lambda.zip dist/index.js

# zipをAWS Lambdaにアップロード
$ aws lambda update-function-code \
    --zip-file fileb://lambda.zip \
    --function-name my-lambda-function-on-aws
```

上記のコマンド・依存性の初期準備が難しい？ 心配は無用です。各種JS処理系の `create` コマンドでプロジェクトの雛形を作れるようになっています。

```bash
$ bun create hono .

create-hono version 0.5.0
✔ Using target directory … .
✔ Which template do you want to use? › aws-lambda
cloned honojs/starter#main to /Users/ksaitou/hono-aws-lambda
✔ Do you want to install project dependencies? … yes
✔ Which package manager do you want to use? › bun
✔ Installed project dependencies
```

その他、Honoには便利機能が満載です。

- [Zod による型安全なリクエストのバリデーション機能](https://hono.dev/guides/validation#with-zod)、[OpenAPI仕様の生成](https://hono.dev/snippets/zod-openapi)
- [卓越したパフォーマンス](https://hono.dev/concepts/benchmarks)
- [Web標準に準拠したAPI](https://zenn.dev/yusukebe/articles/47dea431a00752)
    - ブラウザ上で動かすことも可能
- [multipart/form-data への標準対応](https://hono.dev/api/request#parsebody)
- [環境間でポータブルな環境変数へのアクセスAPI](https://hono.dev/helpers/adapter#env)
- などなど


## AWS Lambda から Cloudflare Workers に夜逃げしてみる

では、実際にAWS Lambda用に作った前述のプロジェクトを [Cloudflare Workers](https://www.cloudflare.com/ja-jp/developer-platform/workers/) にアップロードして動かしてみたいと思います。

Cloudflare Workers のハンドラは以下のようなものなのですが、果たしてHonoはそのまま動くのでしょうか？

```js
export default {
  async fetch(request, env, ctx) {
    return new Response('Hello World!');
  },
}
```

その前にいくつか準備が必要です。

```toml:wrangler.toml
# Cloudflare の設定ファイル
name = "WorkerのID"
compatibility_date = "2023-12-01"
```

```bash
# Cloudflare 関連の依存性の追加
$ bun add -D wrangler @cloudflare/workers-types
```

準備が出来たのでデプロイしてみます。

```bash
$ bun run wrangler deploy --minify src/index.ts
 ⛅️ wrangler 3.36.0
-------------------
Total Upload: 18.93 KiB / gzip: 7.17 KiB
Uploaded WorkerのID (1.40 sec)
Published WorkerのID (0.30 sec)
  https://WorkerのID.ksaitou.workers.dev
Current Deployment ID: WorkerのID
```

動かしてみましょう。

```
$ curl https://WorkerのID.ksaitou.workers.dev/echo
Hello
```

AWS Lambda 用に書いたサーバが Cloudflare Workers でもちゃんと動きました！

## まとめ

Hono を使えばプラットフォーム可搬性のあるTypeScriptによるHTTPサーバを簡単に書くことができます。それぞれのプラットフォームの理解が難しいAPIに触れる必要もありません。

ちょっとした外部連携用のHTTPサーバを書く際など非常に便利なので使ってみましょう。

## 続編も書きました！

https://qiita.com/ssc-ksaitou/items/6815741104185a55e55d