AWS CDK v2でAppSyncを使用したwebアプリを作成する
AppSyncを触る機会があったのですが、AWS CDK で作っていく際に v2 での実装事例があまり多く出てこなかったこともあるので、事例として Web アプリケーションを構築してみようと思いました。
題目としては Using an AWS AppSync API with the AWS CDK - AWS AppSync を参考にして、こちらの記事に従って機能とかを作っていきます。
(Amplify使えばいいじゃんというのは今回なしで、、)
前提
少し内容を端折ったりするので、下記の前提をおいています。
- AWS CDK が何ができるかはわかる
- AWS のサービスがある程度わかる
- Web アプリについて少しわかる
- GraphQL 少し知ってる
端折ったりしても参照情報は置くようにしますが、わかりにくかったらすいません。
ちなみにこの環境を検証するためにかかるコストは 0 円でした(無料枠の範囲内)。
検証環境
- MacBook Air M1 (Sequoia 15.0.1)
- Docker version 27.3.1, build ce12230 (最近は*OrbStack (1.7.5)*から使っていますのでそちらも導入前提で)
- Node v20.10.0
- aws-cli/2.18.15 Python/3.12.7 Darwin/24.0.0 source/arm64 (homebrew で入れてます)
想定する成果物と動作
作るのは掲示板みたいな Web アプリ(適当に sns-sapmle と命名します)で、下記のようなユースケースが実現できる物を作ります。
- ユーザーはログインできる
- ユーザーは内容を Post できる
- ユーザーは Post 一覧を閲覧できる
- ユーザーは別のユーザーの Post を閲覧できる
- ユーザーは他のユーザーからメンションがきたらリアルタイムで通知が来る
実際に今回作成した AWS CDK と Web アプリケーションの実装は下記のリポジトリに置いてありますので、適宜参考にしていただければ幸いです。
アーキテクチャ
Web アプリを S3 にデプロイしてもいいのですが、AWS CDK で AppSync を利用する事例を作るのが目的なので、ローカルに立ち上げた検証環境で Web アプリの動作確認します。
ひとまずドキュメント通り AppSync で AWS CDK で使えるようにこなしていく
いきなり難易度を上がる感じがしますが、まずは公式がいい感じに AWS CDK で AppSync を使えるようにドキュメントを用意していますので、そちらを題材にしてプロジェクトの基礎を作っていきます。
最初に AWS CDK を使ったプロジェクトの準備とかありますが今回は省略します。
下記にプロジェクトのセットアップについて言及しているので参考にしてください。
今回のサンプル例では aws-cdk-appsync-webapp
というプロジェクト名で作成します。
-
TypeScript の基礎から始める AWS CDK 開発入門
- AWS CDK が簡単に使えるようになるレベルの重要なトピックもあるので、こちらおすすめ
- 今回参考とする公式の最初の章
GraphQL スキーマの導入
GraphQL を扱う際に、まずは操作できるクエリとか型とかを定義する必要があります。
こちらの通り、作成された CDK プロジェクトに GraphQL スキーマを導入します。
input CreatePostInput {
title: String
content: String
}
type Post {
id: ID!
title: String
content: String
}
type Mutation {
createPost(input: CreatePostInput!): Post
}
type Query {
getPost: [Post]
}
schema.graphql
を作成したら、ドキュメント通り AWS CDK で AppSync の GraphQL API を定義します。フロントエンド側との接続については後述しますが、API のエンドポイント URL や API Key を出力しておきます。
import * as cdk from 'aws-cdk-lib';
import * as appsync from 'aws-cdk-lib/aws-appsync';
import { Construct } from 'constructs';
export class AwsCdkAppsyncWebappStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// バックエンドのGraphQL APIを作成
const api = new appsync.GraphqlApi(this, "SnsSapmleApi", {
name: "appsync-webapp-api",
schema: appsync.SchemaFile.fromAsset("schema/schema.graphql"),
});
// Prints out URL
new cdk.CfnOutput(this, "GraphQLAPIURL", {
value: api.graphqlUrl,
});
// Prints out the AppSync GraphQL API key to the terminal
new cdk.CfnOutput(this, "GraphQLAPIKey", {
value: api.apiKey || "",
});
// Prints out the stack region to the terminal
new cdk.CfnOutput(this, "Stack Region", {
value: this.region,
});
}
}
ドキュメントではこのまま AWS にデプロイしてしまっていますが、ひとまず作り上げることが目的なのでスルーします。
データソースの追加
GraphQL のスキーマで操作できるクエリとか型とか定義したら、 AppSync はどのデータに対して操作するのかをデータソース(Data Source)として定義します。
要件によってどんなデータソースを使うかは異なりますが、今回は DynamoDB を使うので、 DynamoDB のテーブルを作成します。
import * as cdk from 'aws-cdk-lib';
import * as appsync from 'aws-cdk-lib/aws-appsync';
+import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { Construct } from 'constructs';
export class AwsCdkAppsyncWebappStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// バックエンドのGraphQL APIを作成
const api = new appsync.GraphqlApi(this, "Api", {
});
+ //creates a DDB table
+ const add_ddb_table = new dynamodb.Table(this, "SnsSapmleTable", {
+ partitionKey: {
+ name: "id",
+ type: dynamodb.AttributeType.STRING,
+ },
+ });
}
}
リゾルバの追加
AppSync を使う際の最大の難所(だと思っている)のがリゾルバです。 ここまでで、GraphQL のスキーマを定義して(どんな API なのか)、データソースを定義した(どこに対してデータ操作したいのか)ので、どんなデータ操作するのかをリゾルバとして定義します。
下記一部省略しています。
+ // Creates a function for query
+ const add_func = new appsync.AppsyncFunction(this, "FuncGetPost", {
+ name: "get_posts_func_1",
+ api,
+ dataSource: api.addDynamoDbDataSource("TableForPosts", add_ddb_table),
+ code: appsync.Code.fromInline(`
+ export function request(ctx) {
+ return { operation: 'Scan' };
+ }
+
+ export function response(ctx) {
+ return ctx.result.items;
+ }
+ `),
+ runtime: appsync.FunctionRuntime.JS_1_0_0,
+ });
+ // Adds a pipeline resolver with the get function
+ new appsync.Resolver(this, "PipelineResolverGetPosts", {
+ api,
+ typeName: "Query",
+ fieldName: "getPost",
+ code: appsync.Code.fromInline(`
+ export function request(ctx) {
+ return {};
+ }
+
+ export function response(ctx) {
+ return ctx.prev.result;
+ }
+ `),
+ runtime: appsync.FunctionRuntime.JS_1_0_0,
+ pipelineConfig: [add_func],
+ });
一応の動作確認
ここまでで、AppSync の API を作成して、データソースを定義して、リゾルバを定義しました。
公式に従って、実際に cdk deploy
してさくっと動作確認をします。
公式で説明している内容が実装と異なっている箇所があるので、そこだけ注意してください。
具体的には、type Post
の content: String
でデプロイしたはずが、説明では date: AWSDateTime
になっている点です。
AppSync の Queries から createPost
を実行して、 DynamoDB にデータが挿入されているかを確認します。
続いて、 getPost
を実行して、 DynamoDB に挿入したデータが取得できるかを確認します。
以上で動作確認完了です。
cdk destroy
して、デプロイしたリソースをひとまず削除します。
続いて、これをフロントエンドで使えるようにしていきます。
ドキュメントの仕様に沿ったフロントエンドの作成
公式では AWS CDK による AppSync のバックエンドを簡単に作ることを焦点にしていましたが、より実践的なユースケースとしてフロントエンドも作成します。まずは公式のバックエンドに沿ってフロントエンドを簡単に作り、その後独自の機能を追加実装します。
今回はフロントエンドも AWS CDK で作成して管理するので、Monorepo 構成で作成します。
技術スタックとしては Vite + React でシンプルに高速に作ります。
今までの AWS CDK のバックエンドを frontend_infra
のディレクトリとして分けて、 npm create vite@latest frontend -- --template react-ts
で Vite プロジェクトを作ります。
下記のディレクトリ構造とします。
.
├── frontend
│ .
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── src
│ ├── tsconfig.node.json
│ └── vite.config.ts
└── frontend_infra
.
├── package-lock.json
├── package.json
├── schema
├── test
├── test.eraserdiagram
└── tsconfig.json
ログイン機能を作る
今回のユースケースというか題材が機能もりもりなので、ひとまず現状のバックエンドの構成に合わせて適当にフロントエンドを作り、ログイン機能をもたせます。
まずは AWS CDK で Cognito のユーザープールを作成します。ついでに後々フロントエンド側で使用する CognitoUserPoolId
と CognitoUserPoolClientId
を出力しておきます。
export class AwsCdkAppsyncWebappStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
+ const userPool = new cognito.UserPool(this, "CognitoUserPool", {});
+ const userPoolClient = new cognito.UserPoolClient(this, "CognitoUserPoolClient", {
+ userPool,
+ });
// バックエンドのGraphQL APIを作成
.
.
+ new cdk.CfnOutput(this, "CognitoUserPoolId", {
+ value: userPool.userPoolId,
+ });
+
+ new cdk.CfnOutput(this, "CognitoUserPoolClientId", {
+ value: userPoolClient.userPoolClientId,
+ });
一旦これでデプロイすると Cognito のユーザープールが作成されるので、 AWS コンソールからお試しユーザーを作成してしまいます。
続いて、ログイン機能を簡単に作成するために今回は Amplify を利用します。
Amplify については深く触れませんが、Cognito を用いたユーザー認証したり GraphQL API の定義を生成するために使用したいので、 Gen2 ではなく Gen1 の機能を使います。
(自分がまだ Gen2 を触ったことないのでご容赦ください)
Amplify の UI framework を frontend
に追加します。
npm install @aws-amplify/ui-react aws-amplify
あとは React のコンポーネントを作成するだけなのですが、Amplify が Cognito と接続するための設定をして、<App />
コンポーネントを wrap するだけで完了します。
Gen2 とかだと Amplify のプロジェクト設定して自動生成される設定ファイルを使うと思いますが、今回は情報をベタ書きで設定します。 CDK デプロイ時に出力した CognitoUserPoolId
と CognitoUserPoolClientId
を使って設定します。
+import { Authenticator } from "@aws-amplify/ui-react";
+import "@aws-amplify/ui-react/styles.css";
+Amplify.configure({
+ Auth: {
+ Cognito: {
+ userPoolId: <CognitoUserPoolId>,
+ userPoolClientId: <CognitoUserPoolClientId>,
+ },
+ },
+});
createRoot(document.getElementById("root")!).render(
<StrictMode>
+ <Authenticator>
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
+ </Authenticator>
</StrictMode>
);
<App />
では、画面の綺麗さコードの詳細とかは今回省略しますがMUI を使って画面を作ります。
ログイン後の画面に Sign Out
ボタンを作っておきましょう。その他のコンポーネントについては、後ほど詳細に実装するのでここでは説明しません。単純適当なものなので気になる人はレポジトリを参考にしていただければ。
import { useAuthenticator } from "@aws-amplify/ui-react";
import { Button } from "@mui/material";
function App() {
const { signOut } = useAuthenticator();
return (
<Container maxWidth="sm">
<Button onClick={signOut}>Sign out</Button>
<Box sx={{ my: 4 }}>
<Notification />
<PostForm />
<PostList />
</Box>
</Container>
);
}
frontend
ディレクトリで npm run dev
を実行すると、ログイン画面が表示されます。
先ほど Cognito で作成したユーザーでログインしてみて、下記のように「Sign Out」ボタン付きの画面が表示されたらログイン機能完成です。
Post 投稿の API を実装してみる
ログインしてもサンプルのデータがあるだけなので、AppSync の API を叩くように実装します。
この API との接続について、AppSync の AWS コンソール上でアプリと統合の手順が記載されているので、これを参考に実施します。
すでに Cognito との統合で Amplify.configure
を設定しているので、追記する形で API を設定します。
AWS CDK で AppSync の API を作成したとき、すでに CfnOutput
で API のエンドポイント URL と API Key を出力しているので、それを追記します。
Amplify.configure({
+ API: {
+ GraphQL: {
+ endpoint: <GraphQLAPIURL>,
+ region: 'ap-northeast-1',
+ defaultAuthMode: 'apiKey',
+ apiKey: <GraphQLAPIKey>,
+ }
+ }
});
あとは下記コマンドを frontend
ディレクトリで実行して、フロントエンド開発に必要な型定義情報とか自動で出力します。
npx @aws-amplify/cli codegen add --apiId xxxxxxxxxxxxx
? Choose the type of app that you're building javascript
? What javascript framework are you using react
✖ Getting API details
AppSync API was not found in region us-east-1
? Do you want to choose a different region Yes
? Choose AWS Region Asia Pacific (Tokyo)
✔ Getting API details
? Choose the code generation language target typescript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.ts
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
? Enter the file name for the generated code src/API.ts
? Do you want to generate code for your newly created GraphQL API Yes
実行後に src/API.ts
が生成されているので、これを使ってフロントエンド側で API を叩くように、まずは createPost
から実装します。
下記は実装の一例ですが、 PostForm
コンポーネントを作成して、フォームの入力値を createPost
に渡しています。
const client = generateClient();
const PostForm = () => {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const handlePost = async () => {
const result = await client.graphql({
query: createPost,
variables: { input: { content: content, title: title } },
});
setContent("");
setTitle("");
};
return (
<Container>
<TextField label="Title" variant="filled" value={title} onChange={(e) => setTitle(e.target.value)} />
<TextField
label="New Post"
multiline
maxRows={4}
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<Button onClick={handlePost}>Post</Button>
</Container>
);
};
export default PostForm;
試しにこれで npm run dev
して、フォームに入力して投稿してみます。
デベロッパーツールで確認するとなんかいい感じに API を実行できているのが確認できます。
DynamoDB にもデータが挿入されているのが確認できます。
Post 一覧の API を実装してみる
本来であれば listPosts
という API を実装するところですが、公式に従って実装した AWS CDK による AppSync のバックエンドでは、 getPost
という query 名であるにも関わらず DynamoDB に対して Scan
を実行しているので、これを叩くようにフロントエンドを実装します。
このとき、 Amplify のクライアントを React App のすべての場所で利用したいので、Context Provider として定義し直します。
import { generateClient } from "aws-amplify/api";
import { createContext, useContext } from "react";
type AmplifyClientType = ReturnType<typeof generateClient>;
export const AmplifyClientContext = createContext<AmplifyClientType | null>(null);
export const AmplifyClientProvider = ({ children: children }: { children: React.ReactNode }) => {
const AmplifyClient = generateClient();
return (
<AmplifyClientContext.Provider value={AmplifyClient}>
{children}
</AmplifyClientContext.Provider>
);
};
export const useAmplifyClient = () => {
const context = useContext(AmplifyClientContext);
if (!context) {
throw new Error("useAmplifyClient must be used within a AmplifyClientProvider");
}
return context;
};
Context Provider の利用に伴い、既存の PostForm
も Context として提供される AmplifyClient を利用するように修正します。
+import { useAmplifyClient } from "../contexts/AmplifyClientContext";
-const client = generateClient();
const PostForm = () => {
+ const client = useAmplifyClient();
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
PostList.tsx
のコンポーネントに対して、 getPost
を下記のように実装できます。
+import { getPost } from "../graphql/queries";
+import { useAmplifyClient } from "../contexts/AmplifyClientContext";
+import type { Post } from "../API";
const PostList = () => {
+ const client = useAmplifyClient();
const [posts, setPosts] = useState<Post[]>([]);
+ useEffect(() => {
+ const posts = async () => {
+ const result = await client.graphql({
+ query: getPost,
+ });
+ setPosts(result.data?.getPost as Post[] || []);
+ };
+ posts().catch(console.error);
+ }, []);
これで初期レンダリング時に getPost
を叩いて Post 一覧を取得して表示できるようになりました。
Post 追加するたびにブラウザをリロードして確認するのが面倒なので、更新ボタンも追加しています(getPost
を叩くゴミ実装なのはサンプルなので許してください)。
下記のように、POST ボタンを押下して適当な投稿をしたあと、GET POSTS を押下して先ほど入力した Post が一覧に表示されることを確認します。
残りの追加の API とフロントエンドの実装
ここまでで AWS CDK での AppSync バックエンドに従ったフロントエンド実装ができたので、次は独自の機能を追加していきます(やっと本番?)。
残りの機能は下記のとおりで、順番に実装します。
- ユーザーは別のユーザーの Post を閲覧できる(
listPostsByUser
) - ユーザーは他のユーザーからメンションがきたらリアルタイムで通知が来る(
subscribeToPostNotifications
)
今回は amplify-cli を使ってフロントエンドの型定義とかを生成しているので、AWS CDK で AppSync のバックエンドに機能実装してからフロントエンド実装するという流れでやっていきます。
listPostsByUser を実装する
まずは GraphQL のスキーマから作ります。
Post
の型に userId
を追加して、さらに userId
を input としてクエリできるように listPostsByUser
を追加します。
type Post {
id: ID!
+ userId: String!
title: String
content: String
}
type Query {
getPost: [Post]
+ listPostsByUserId(userId: String!): [Post]
}
続いて関数とリゾルバを追加・修正します。
DynamoDB のテーブル設計について触れていませんでしたが、今回のお題では Post の id
がパーティションキーになっています。
そのため、このままでは userId
を検索するときに Scan が毎回発生してしまうので、userId
で DynamoDB にクエリできるようにuserId
をパーティションキーとしたグローバルセカンダリインデックスを追加で作成します。(参考)
また、すでに例で作成している AWS CDK では、 JavaScript リゾルバを扱う際に Pipeline Resolver を使用して inline code で実装されています。しかし、Unit Resolver もサポートされているので、ここでは Unit Resolver を使用してコードを外部定義して実装する例にしたいと思います。
リゾルバのコードは TypeScript でも実装できますが、今回は JavaScript で実装します(こちらやこちらが参考になります)。
frontend_infra
で npm install @aws-appsync/utils
リゾルバ作成に必要なパッケージを追加します。
続いて src/resolvers/listPostsByUserId.js
を作成して、リゾルバを実装します。
import * as ddb from "@aws-appsync/utils/dynamodb";
// 公式ドキュメントでは、 TypeScript 実装の場合は Amplify を使って、
// 型定義情報を生成して ctx の型として利用しています
export function request(ctx) {
const { limit = 10, nextToken, userId } = ctx.args;
return ddb.query({
index: "gsi-userId",
limit: limit,
query: {
userId: { eq: userId },
},
nextToken: nextToken,
});
}
export function response(ctx) {
const { items: posts = [], nextToken } = ctx.result;
return { posts, nextToken };
}
これを CDK で Unit Resolver として追加します。
+ new appsync.Resolver(this, "UnitResolverListPostsByUserId", {
+ api,
+ typeName: "Query",
+ fieldName: "listPostsByUserId",
+ dataSource: snsSampleTableDs,
+ code: appsync.AssetCode.fromAsset("resolvers/listPostsByUserId.js"),
+ runtime: appsync.FunctionRuntime.JS_1_0_0,
+ });
ここまできたら cdk deploy
してデプロイします。
バックエンドをデプロイしているので、あとはフロントエンド側で query を実装するために、frontend
で npx @aws-amplify/cli codegen
を実行して型定義情報を更新しておきます。
続いて既存の PostForm.tsx
にて、 Post 時にユーザー ID を含めるように修正します。
const handlePost = async () => {
+ const userId = (await getCurrentUser()).userId;
const result = await client.graphql({
query: createPost,
+ variables: { input: { content: content, title: title, userId: userId } },
});
setContent("");
setTitle("");
};
新しく UI を作るのが面倒なので、検索結果については既存の PostList.tsx
をそのまま使います。
テストで登録した DynamoDB のテーブルのデータは、userId がないので AWS コンソールから削除しておくと良いと思います。
まずは動作としてすでに作成しているユーザーで適当にいくつか Post します。
続いて、AWS コンソール上の Cognito から新しく適当にユーザーを作成して、そのユーザーで再度サインインして Post してみます。
下記のような形で、異なるユーザーが Post されていることが確認できます。
それではいよいよ、User ID の Text Field に特定の User ID を入力してフィルタする形で Post 一覧を取得してみます。
下記が、新しく作ったユーザー(177~
のやつ)がログインして、既存のユーザーの Post を取得している様子です。
subscribeToPostNotifications を実装する
ここまで来たら最後はリアルタイム通知の実装です。
簡単に実装するためにロジックは複雑にしませんが、 Post のコンテンツに @userId を含む場合は、その userId の人にリアルタイムで通知が来るようにします。
少し寄り道しますが、AppSync のクエリを実行する際に API キーによる認証を使っていました。このままでも良いのですが、実際の場面では AppSync の API は Cognito で認証されたユーザーからのみリクエストを受け付ければ良いので、API キーによる認証を Cognito による認証に変更します。
(GraphQL を保護するための認証と認証の設定 APIs - AWS AppSync)
余談ですが、公式ドキュメントから実装した GraphqlApi では schema 設定が deprecated だったのでついでに更新しています。
const api = new appsync.GraphqlApi(this, "SnsSapmleApi", {
name: "appsync-webapp-api",
+ schema: appsync.Definition.fromFile("schema/schema.graphql"),
+ authorizationConfig: {
+ defaultAuthorization: {
+ authorizationType: appsync.AuthorizationType.USER_POOL,
+ userPoolConfig: {
+ userPool: userPool,
+ },
+ },
},
});
フロントエンド側の Amplify の設定も Cognito に変更しておきます。
Amplify.configure({
API: {
GraphQL: {
endpoint: <GraphQLAPIURL>,
region: 'ap-northeast-1',
- apiKey: <GraphQLAPIKey>,
+ defaultAuthMode: "userPool",
}
}
});
これで API キーを利用せずに、 Cognito の User Pool で認証されたユーザーからのリクエスト API を叩けるようになりました。
本題に戻り、GraphQL の Subscription を実装します。
CreatePost の実行を subscribe し、その Post 内の toUserId が自分のものであればリアルタイムで通知するようにします。
input CreatePostInput {
userId: String!
title: String
content: String
+ toUserId: String
}
type Post {
id: ID!
userId: String!
title: String
content: String
+ toUserId: String
}
+type Subscription {
+ onCreatePost(toUserId: String!): Post
+ @aws_subscribe(mutations: ["createPost"])
+}
スキーマの定義自体はご覧の通り簡単です。 クライアント側でどのような情報を subscribe するのかが肝になるので、フロントエンド側を重点的に見ていきましょう。
まずは npx @aws-amplify/cli codegen
を実行して、型定義情報を更新します。
続いて、バックエンド側で Cognito の User Pool で認証されたユーザーからのリクエストを受け付けるようにしているので、 generateClient に authMode: "userPool"
を設定します。
また、今回 Subscription で利用する userId は PostForm
のコンポーネントでも取得・使用しているので、共通化してコンテキストで管理してしまいます。
+type AmplifyClientContextType = {
+ AmplifyClient: AmplifyClientType;
+ userId: string;
+};
+export const AmplifyClientContext = createContext<AmplifyClientContextType | null>(
+ null
+);
export const AmplifyClientProvider = ({ children }) => {
+ const AmplifyClient = generateClient({
+ authMode: "userPool",
+ });
+ const [userId, setUserId] = useState("");
+ const [loading, setLoading] = useState(true);
+ useEffect(() => {
+ (async () => {
+ const user = await getCurrentUser();
+ setUserId(user.userId);
+ setLoading(false);
+ })();
+ }, []);
+ if (loading) {
+ return <div>Loading...</div>; // ローディング状態のUIを表示
+ }
return (
+ <AmplifyClientContext.Provider value={{ AmplifyClient, userId }}>
{children}
</AmplifyClientContext.Provider>
);
}
これで Web アプリにログインした後、 userId を取得して利用できるようになりました。
最後に、subscribe で通知バーが表示されるようにコンポーネントを作成します。
import { useEffect, useState } from "react";
import { Snackbar } from "@mui/material";
import { onCreatePost } from "../graphql/subscriptions";
import { useAmplifyClient } from "../contexts/AmplifyClientContext";
const Notification = () => {
const {AmplifyClient, userId} = useAmplifyClient();
const [mention, setMention] = useState<string | null>(null);
useEffect(() => {
const subscription = AmplifyClient.graphql({
query: onCreatePost,
variables: {
toUserId: userId,
},
})
.subscribe({
next: ({ data }) => {
const post = data.onCreatePost.content;
const fromUserId = data.onCreatePost.userId;
setMention(`New post from ${fromUserId}: ${post}`);
},
error: (error) => {
console.warn("Subscription error:", error);
},
});
// Cleanup the subscription on component unmount
return () => {
subscription.unsubscribe();
};
}, []);
return (
<Snackbar
open={Boolean(mention)}
message={mention}
autoHideDuration={4000}
onClose={() => setMention(null)}
/>
);
};
export default Notification;
いい感じにフロントエンドも実装できたので、DynamoDB のテーブルデータを一旦削除して、2 画面でそれぞれ別のユーザーでログインしてリアルタイム通知の動作確認をします。
下記動作の様子になります。
ご覧の通り、相手のユーザーに対して @
でメンションを飛ばし、対象のユーザーが通知バーで内容が表示されることを確認できました。
さいごに
本記事では、AWS CDK v2 を使った AppSync の GraphQL API の実装事例を Web アプリケーションを題材として紹介しました。
題材としては AppSync の AWS CDK 実装の公式ドキュメントを基にしましたが、ログインやリアルタイム通知など現実の場で必要となる機能も併せて事例として紹介できたかなと思います。
AppSync のキャッシング、DynamoDB のテーブル設計などのパフォーマンスチューニングや、WAF の利用などのセキュリティ強化などが深掘りポイントとして考えられるので、誰かの参考になれば幸いです。
Discussion