Amplify Gen2でバックエンドにREST APIだけを使ったシンプルなWebアプリ開発
こんにちは。株式会社シータグCTOの @y_okady です。
シータグの社内システムはkintoneやLaravelで構築されているものが多いのですが、最近新しく社内システムを開発する機会があり、2024年5月にGAとなったAmplify Gen2を使ってみることにしました。
Amplify Gen2公式のチュートリアルで利用されているテンプレートでは、バックエンドにDynamoDBとCognitoを使用する構成になっています。しかし今回はDynamoDBもCognitoも不要で、外部APIを実行するためのバックエンドと、フロントエンドとバックエンドでデータをやり取りするためのREST APIが必要でした。
この記事では、公式のチュートリアルに従ってセットアップを進めつつ、DynamoDBとCognitoの使用をやめ、代わりにREST API(API GatewayとLambda)を使用する方法を紹介します。
公式のチュートリアルに従ったセットアップ
今回はNext.js App Routerを使用するチュートリアルに従ってセットアップを進めました。
どのフレームワークを選択しても、利用するテンプレートが異なるだけでセットアップ手順に大きな差異はないかと思います。Next.js App Routerを使用するチュートリアルの手順は以下の通りです。
- テンプレートからリポジトリの作成
- Amplifyコンソールでデプロイ対象のリポジトリを指定してデプロイ
- デプロイされたWebアプリの確認
- ローカルでのフロントエンド開発環境のセットアップ
- フロントエンドのソースコードの変更(関数の実装)
- フロントエンドのソースコードの変更(UIの実装)
- ローカルでのバックエンド開発の環境のセットアップ
- クラウドサンドボックスのデプロイ
- バックエンドのソースコードの変更
Amplifyではリポジトリにpushすると自動でWebアプリがデプロイされるようになっており、テンプレートからリポジトリを作成してAmplifyコンソールでデプロイ対象のリポジトリを指定をするだけでまず初回のデプロイが実行されます(手順2)。また、上記手順8のクラウドサンドボックスを利用しなくても、毎回リポジトリにpushすることでバックエンドの動作を確認することは可能ですが、デプロイには5分ほど時間がかかってしまうため開発中はクラウドサンドボックスの利用をオススメします。
クラウドサンドボックスを利用しない場合、デプロイ後に生成される amplify_outputs.json
をAmplifyのコーソールからダウンロードして使用します(手順4)。クラウドサンドボックスを利用する場合は npx ampx sandbox
を実行すれば amplify_outputs.json
が自動生成されます(手順8)。いずれの場合でも、ローカルでWebアプリを動かすには npm run dev
を実行します。
DynamoDBとCognitoの使用をやめる
バックエンドでどのサービスを使用するかは amplify/backend.ts
で定義します。テンプレートからリポジトリを作成した直後はauth(Cognito)とdata(DynamoDB)を使用する設定になっているため、これらを削除します。また、amplify
ディレクトリ以下の auth
と data
ディレクトリも不要ですので削除します。
import { defineBackend } from '@aws-amplify/backend';
// import { auth } from './auth/resource.js';
// import { data } from './data/resource.js';
defineBackend({
// auth,
// data,
});
REST APIを使用する
Amplifyのドキュメントに「API (REST)」というページがあり、セットアップ方法から使用方法まで必要な情報はすべてまとまっています。
今回はAPI GatewayでHTTP APIを使用することにしたので、以下のページに従ってセットアップを進めました。
RESTを使うためだけにこんなに色々書かないといけないの…?と心が折れそうになりましたが、とりあえずまるっとコピペして不要な部分を削除することにしました。まずは amplify/functions/<function名>
ディレクトリ以下に、resource.ts
と handler.ts
を作成します。ここではfunction名を api-function
とします。REST APIのHTTPメソッドやパスによって処理を変える方法がよくわからず、とりあえず event.routeKey
の値で分岐するという泥臭い書き方にしました。
import { defineFunction } from "@aws-amplify/backend";
export const apiFunction = defineFunction();
import type { APIGatewayProxyHandlerV2 } from "aws-lambda";
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
let body = {};
switch (event.routeKey) {
case 'GET /':
body = await getDataFromBackend();
break;
case 'POST /':
body = await postDataFromBackend(JSON.parse(event.body));
break;
default:
throw new Error();
}
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
},
body: JSON.stringify(body),
};
};
続いて amplify/backend.ts
でバックエンドで利用するサービスを定義します。公式ドキュメント上のサンプルコードから、CognitoやIAMに関する処理を削除しています。ここではAPI名を sample-api
としています。
import { defineBackend } from "@aws-amplify/backend";
import { Stack } from "aws-cdk-lib";
import { CorsHttpMethod, HttpApi, HttpMethod } from "aws-cdk-lib/aws-apigatewayv2";
import { HttpLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";
import { apiFunction } from "./functions/api-function/resource";
const backend = defineBackend({
apiFunction,
});
const apiStack = backend.createStack("api-stack");
const httpLambdaIntegration = new HttpLambdaIntegration(
"LambdaIntegration",
backend.apiFunction.resources.lambda
);
const httpApi = new HttpApi(apiStack, "HttpApi", {
apiName: "sample-api",
corsPreflight: {
allowMethods: [
CorsHttpMethod.GET,
CorsHttpMethod.POST,
CorsHttpMethod.PUT,
CorsHttpMethod.DELETE,
],
allowOrigins: ["*"],
allowHeaders: ["*"],
},
createDefaultStage: true,
});
httpApi.addRoutes({
path: "/",
methods: [HttpMethod.GET],
integration: httpLambdaIntegration,
});
httpApi.addRoutes({
path: "/",
methods: [HttpMethod.POST],
integration: httpLambdaIntegration,
});
backend.addOutput({
custom: {
API: {
[httpApi.httpApiName!]: {
endpoint: httpApi.url,
region: Stack.of(httpApi).region,
apiName: httpApi.httpApiName,
},
},
},
});
最後に、フロントエンドでREST APIを実行できるよう設定します。フレームワークに応じて適切なファイルに以下の処理を記述します。
import { Amplify } from "aws-amplify";
import { parseAmplifyConfig } from "aws-amplify/utils";
import { get, post } from 'aws-amplify/api';
import outputs from "@/amplify_outputs.json";
const amplifyConfig = parseAmplifyConfig(outputs);
Amplify.configure({
...amplifyConfig,
API: {
...amplifyConfig.API,
REST: outputs.custom.API,
},
});
セットアップはここまでで、あとはREST APIを実行する処理を記述します。Fetch APIを用いた一般的な書き方はできず、Amplifyが用意しているgetやpostメソッドを使用します。
import { get, post } from 'aws-amplify/api';
const getDataFromFrontend = () => {
const httpOperation = get({
apiName: 'sample-api',
path: '/',
});
return httpOperation.response.then((resp) => resp.body.json());
};
const postDataFromFrontend = (body) => {
const httpOperation = post({
apiName: 'sample-api',
path: '/',
options: {
body,
}
});
return httpOperation.response.then((resp) => resp.body.json());
};
おわりに
以上の手順で、Amplify Gen2でバックエンドにREST APIだけを使ったシンプルなWebアプリを動かすことができました。
Amplify Gen2でデータベースの必要なWebアプリを開発する場合は、DynamoDBを使用することでGraphQL経由でシームレスなデータのやり取りが可能です。また、Cognitoを使用した認証機能も簡単に実装できます。
REST APIを使用するには上述のような準備が必要だったり、バックエンドでAmplify以外のライブラリを使うには Lambdaレイヤー を使用する必要があったりと少々手間はかかりますが、リポジトリにpushするだけで自動でデプロイされたり、運用コストを抑えられるのは魅力的だと感じました。
軽量なWebアプリを手軽かつ手頃に開発・運用したい方はぜひAmplify Gen2を試してみてください👍
Discussion