コンテンツへスキップ

AWS Lambda

AWS Lambdaは、Amazon Web Servicesによるサーバーレスプラットフォームです。イベントに応答してコードを実行でき、基盤となるコンピューティングリソースは自動的に管理されます。

Honoは、Node.js 18+環境でAWS Lambda上で動作します。

1. セットアップ

AWS Lambdaでアプリケーションを作成する際、IAMロール、API Gatewayなどの関数を設定するには、CDKが便利です。

cdk CLIを使用してプロジェクトを初期化します。

sh
mkdir my-app
cd my-app
cdk init app -l typescript
npm i hono
mkdir lambda
touch lambda/index.ts
sh
mkdir my-app
cd my-app
cdk init app -l typescript
yarn add hono
mkdir lambda
touch lambda/index.ts
sh
mkdir my-app
cd my-app
cdk init app -l typescript
pnpm add hono
mkdir lambda
touch lambda/index.ts
sh
mkdir my-app
cd my-app
cdk init app -l typescript
bun add hono
mkdir lambda
touch lambda/index.ts

2. Hello World

lambda/index.tsを編集します。

ts
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'

const app = new Hono()

app.get('/', (c) => c.text('Hello Hono!'))

export const handler = handle(app)

3. デプロイ

lib/cdk-stack.tsを編集します。

ts
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as apigw from 'aws-cdk-lib/aws-apigateway'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'

export class MyAppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const fn = new NodejsFunction(this, 'lambda', {
      entry: 'lambda/index.ts',
      handler: 'handler',
      runtime: lambda.Runtime.NODEJS_20_X,
    })
    fn.addFunctionUrl({
      authType: lambda.FunctionUrlAuthType.NONE,
    })
    new apigw.LambdaRestApi(this, 'myapi', {
      handler: fn,
    })
  }
}

最後に、デプロイするコマンドを実行します。

sh
cdk deploy

バイナリデータの提供

Honoは、レスポンスとしてバイナリデータをサポートしています。Lambdaでは、バイナリデータを返すにはbase64エンコーディングが必要です。バイナリタイプがContent-Typeヘッダーに設定されると、Honoは自動的にデータをbase64にエンコードします。

ts
app.get('/binary', async (c) => {
  // ...
  c.status(200)
  c.header('Content-Type', 'image/png') // means binary data
  return c.body(buffer) // supports `ArrayBufferLike` type, encoded to base64.
})

AWS Lambdaオブジェクトへのアクセス

Honoでは、LambdaEventLambdaContext型をバインドし、c.envを使用してAWS Lambdaイベントとコンテキストにアクセスできます。

ts
import { Hono } from 'hono'
import type { LambdaEvent, LambdaContext } from 'hono/aws-lambda'
import { handle } from 'hono/aws-lambda'

type Bindings = {
  event: LambdaEvent
  lambdaContext: LambdaContext
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/aws-lambda-info/', (c) => {
  return c.json({
    isBase64Encoded: c.env.event.isBase64Encoded,
    awsRequestId: c.env.lambdaContext.awsRequestId,
  })
})

export const handler = handle(app)

RequestContextへのアクセス

Honoでは、LambdaEvent型をバインドし、c.env.event.requestContextを使用してAWS Lambdaリクエストコンテキストにアクセスできます。

ts
import { Hono } from 'hono'
import type { LambdaEvent } from 'hono/aws-lambda'
import { handle } from 'hono/aws-lambda'

type Bindings = {
  event: LambdaEvent
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/custom-context/', (c) => {
  const lambdaContext = c.env.event.requestContext
  return c.json(lambdaContext)
})

export const handler = handle(app)

v3.10.0以前(非推奨)

ApiGatewayRequestContext型をバインドし、c.env.を使用してAWS Lambdaリクエストコンテキストにアクセスできます。

ts
import { Hono } from 'hono'
import type { ApiGatewayRequestContext } from 'hono/aws-lambda'
import { handle } from 'hono/aws-lambda'

type Bindings = {
  requestContext: ApiGatewayRequestContext
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/custom-context/', (c) => {
  const lambdaContext = c.env.requestContext
  return c.json(lambdaContext)
})

export const handler = handle(app)

Lambdaレスポンスストリーミング

AWS Lambdaの呼び出しモードを変更することで、ストリーミングレスポンスを実現できます。

diff
fn.addFunctionUrl({
  authType: lambda.FunctionUrlAuthType.NONE,
+  invokeMode: lambda.InvokeMode.RESPONSE_STREAM,
})

通常、実装にはawslambda.streamifyResponseを使用してNodeJS.WritableStreamにチャンクを書き込む必要がありますが、AWS Lambdaアダプターを使用すると、handleの代わりにstreamHandleを使用することで、Honoの従来のストリーミングレスポンスを実現できます。

ts
import { Hono } from 'hono'
import { streamHandle } from 'hono/aws-lambda'

const app = new Hono()

app.get('/stream', async (c) => {
  return streamText(c, async (stream) => {
    for (let i = 0; i < 3; i++) {
      await stream.writeln(`${i}`)
      await stream.sleep(1)
    }
  })
})

const handler = streamHandle(app)

MITライセンスの下でリリースされています。