🙆‍♀️

AWS Amplify HostingでHonoを使ったフルスタックウェブアプリケーションを構築する

2024/09/28に公開

AWS Amplify HostingはNext.jsやNuxt.jsのフレームワークのSSRを独自にサポートしていますが。この機能(Compute)の動作仕様は公開されています。

https://docs.aws.amazon.com/amplify/latest/userguide/ssr-deployment-specification.html

これを利用して、Node.jsとHonoで作ったAPIサーバーをデプロイしてフルスタックなウェブアプリケーションを作ることができます。

必要なファイルだけを最小限に配置した例が以下になります。

├── amplify.yml
└── app
    ├── compute
    │   └── default
    │       ├── index.js
    │       ├── node_modules
    │       ├── package-lock.json
    │       └── package.json
    ├── deploy-manifest.json
    └── static
        ├── index.html
        └── styles.css

※実際の開発ではビルドツールを使ってバンドルした結果が上記app/ディレクトリになるのが理想です。説明のためにこのような変則的な構成にしています。

amplify.yml

amplify.ymlはAmplifyのビルド設定ファイルです。git pushするとこの設定どうりにAWS環境でビルドが行われます。

version: 1
frontend:
    phases:
        build:
            commands:
                - 'cd app/compute/default/ && npm install'
    artifacts:
        baseDirectory: app
        files:
            - '**/*'
    cache:
        paths:
            - '.npm/**/*'

アップロード対象のディレクトリにnode_modulesを配置する必要があるため 'cd app/compute/default/ && npm install' というコマンドを実行しています。

deploy-manifest.json

これが最も重要なファイルです。deploy-manifest.jsonのにはAWSビルド環境でリソースをどのように配置するかの設定が書かれています。

deploy-manifest.json
{
  "version": 1,
  "framework": { "name": "hono", "version": "4.6.3" },
  "routes": [
    {
      "path": "/api/*",
      "target": {
        "kind": "Compute",
        "src": "default"
      }
    },
    {
      "path": "/*",
      "target": {
        "kind": "Static"
      }
    }
  ],
  "computeResources": [
    {
      "name": "default",
      "runtime": "nodejs20.x",
      "entrypoint": "index.js"
    }
  ]
}

上記では、/api/* のリクエストは app/compute/default/index.js で実行した APIにルーティングされます。

それ以外のリクエストは app/static 以下の静的ファイルを返します。これはCloudFrontから静的なファイルとして配信されます。今回はここにindex.htmlとstyles.cssを配置しました。

(もちろん全てのページ単位のリクエストをHonoで動かしているNode.jsのサーバーに向け、そこでHTMLを返してMPAとして画面を作ることもできます)

app/compute/default/

Computeリソースの起点となるディレクトリです。このディレクトリ以下がAWS Lambda(もしくは@Edge)にデプロイされます。このLambda関数はAWS側で管理されているのでユーザーからは直接編集することはできません。

index.js
const { Hono } = require('hono');
const { serve } = require('@hono/node-server');

const api = new Hono().basePath('/api');

// TODOアプリのためのインメモリストレージ
const todos = [];

// TODOの取得
api.get('/todos', (c) => c.json(todos));

// TODOの追加
api.post('/todos', async (c) => {
  const { title } = await c.req.json();
  const newTodo = { id: todos.length + 1, title, completed: false };
  todos.push(newTodo);
  return c.json(newTodo, 201);
});

// TODOの更新
api.put('/todos/:id', async (c) => {
  const id = parseInt(c.req.param('id'));
  const { title, completed } = await c.req.json();
  const todo = todos.find(t => t.id === id);
  if (!todo) {
    return c.json({ error: 'Todo not found' }, 404);
  }
  todo.title = title || todo.title;
  todo.completed = completed !== undefined ? completed : todo.completed;
  return c.json(todo);
});

// TODOの削除
api.delete('/todos/:id', (c) => {
  const id = parseInt(c.req.param('id'));
  const index = todos.findIndex(t => t.id === id);
  if (index === -1) {
    return c.json({ error: 'Todo not found' }, 404);
  }
  todos.splice(index, 1);
  return c.json({ message: 'Todo deleted successfully' });
});

const port = 3000
console.log(`Server is running on port ${port}`)

serve({
  fetch: api.fetch,
  port
})

なんの変哲もないHonoのAPIサーバーです。サーバー側で状態を保つことを表すためにインメモリの配列にデータを保存しました。

app/static/

静的ファイルを配置するディレクトリです。上記のAPIを使うためにindex.htmlとstyles.cssで簡単なUIを作りました。

(ソースコードは省略)

デプロイ

Amplifyのコンソールで設定したリポジトリにプッシュすると、Amplifyが自動的にデプロイを開始します。

これらを通して実際にデプロイされたウェブアプリケーションは以下のURLで見ることができます。

https://main.d34bg7pb0ejo8j.amplifyapp.com/

UIは特に見るべき部分はないので、開発者コンソールでAPI通信をしている部分と入力したデータがサーバー側に保持されていることを確認してください。

発展的な話題

static/index.htmlがホストできるということはここからSPAが構築できるということです。Vite+ReactでSPAを構築して、複数の画面に遷移するするアプリケーションにすることができます。

それに、APIを動かしているサーバー部分でAmplifyの関連サービスのSDKを組み込み、依存をサーバーサイドのみに分離する設計も作れます。

また、本来baseDirectoryはgitリポジトリには含めず、ビルド時に生成してバンドルした結果を配置するべきです。そうするとTypeScriptを使うことができます。

注意点としてはcompute/default サブディレクトリは、圧縮して 220 MB を超えることはできません。

参考文献

https://docs.aws.amazon.com/amplify/latest/userguide/deploy-express-server.html

似た話題

https://zenn.dev/laiso/articles/1e859a5a728202

https://zenn.dev/laiso/articles/c7eba95ce43feb

https://zenn.dev/laiso/articles/8c619c38bd7b7b

Discussion