AWS Lambda(with AWS CDK)とHonoでサーバーレスなWebページを作ってみた
HonoがAWS Lambdaで動くことがわかったので、HTMLを配信するための設定を試しました。CDKとLambdaを使用してHonoアプリをセットアップし、HTMLを返すRouteを追加しました。サーバー側でHTMLを組み立てて表示させるため、metaタグなども作成可能です。AWS CDKでデプロイして、/testなどのパスにアクセスするとHTMLが表示されます。また、APIを呼び出して結果を表示する動的な処理を追加することもできます。HonoはUIとAPIをまとめて作成できる軽量なツールです。ただし、実務で使用する場合はTwigやJSXなどでHTMLを構築する仕組みを用意する必要があります。参考記事もあります。
目次
HonoがAWS Lambga (with functions URL)で動くことがわかったので、今度はHTMLを配信できないか試してみました。
Honoアプリのセットアップ
Honoのドキュメントにあるガイドに従ってアプリをセットアップしましょう
% mkdir my-app
% cd my-app
% cdk init app -l typescript
% npm i hono
% mkdir lambda
ここまではAPIを作る時と全く同じです。
CDKのスタック定義も、APIと同様LambdaとLambdaのfunctions URLだけにしました。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Architecture, FunctionUrlAuthType } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
export class HonoOnAwsStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const lambda = new NodejsFunction(
this,
'hono-app',
{
entry: './lambda/main.ts',
handler: 'handler',
architecture: Architecture.ARM_64
}
)
const url = lambda.addFunctionUrl({
authType: FunctionUrlAuthType.NONE,
})
}
}
HTMLを返すRouteをHonoに追加する
HTMLを返すRouteは、app.get
でhono/html
を利用します。
import { Hono } from "hono";
import { handle } from "hono/aws-lambda"
import { html } from 'hono/html'
const app = new Hono()
app.get('/*', c => {
return c.html(
html`<!DOCTYPE html>
<h1>Hello!</h1>
<p>The current router is ${app.routerName}</p>
`
)
})
export const handler = handle(app)
サーバー側でHTMLを組み立てるので、metaタグなどもここで作れます。
app.get('/*', c => {
return c.html(
html`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>サイトタイトル</title>
<meta name="description" content="ページ: ${c.req.path}のデモです">
</head>
<body>
<h1>ページ: ${c.req.path}のデモです</h1>
</body>
</html>
`
)
})
AWS CDKでデプロイ
あとはcdk deploy --hotswap
などでデプロイするだけです。
/test
などのパスにアクセスすると、HTMLが表示されます。
クライアント側で、APIを呼び出す
サーバー側でHTMLを作って表示させているため、script
タグを含めれば動的な処理を追加できます。
例えば、/api
にGETリクエストを送り、その結果を表示する処理を追加しましょう。
import { Hono } from "hono";
import { handle } from "hono/aws-lambda"
import { html } from 'hono/html'
const app = new Hono()
app.get('/*', c => {
return c.html(
html`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>サイトタイトル</title>
<meta name="description" content="ページ: ${c.req.path}のデモです">
</head>
<body>
<h1>ページ: ${c.req.path}のデモです</h1>
<div id="content"></div>
<script>
fetch('/api')
.then(data => data.json())
.then(response => {
const target = document.getElementById("content")
const elm = document.createElement('pre')
elm.textContent = JSON.stringify(response)
target.appendChild(elm)
})
</script>
</body>
</html>
`
)
})
export const handler = handle(app)
そしてHonoでGET /api
も追加します。
app.get('/api', c => {
console.log(c)
return c.json({
path: "hello hono",
})
})
デプロイすると、APIのレスポンスもページに表示されます。
試してみて
簡単なワークショップなどでは、UIもAPIもまとめて作れる & 軽量なので「もうHonoでいいんじゃないか?」感があります。集客力の強さや機能面の関係で、Next.jsの出番が減ることはまだない気もしますが・・・
とはいえ実務で運用に載せるには、HTMLをTwigなりJSXなりで構築する仕組みを用意しないとちょっとつらいかもしれません。