【NextJS】Access AppSync API with DynamoDB using Amplify(Gen1)

1. 概要

前回はAmplify(Gen2)やAppSyncを使い、DynamoDBを操作する内容でした。今回は前回作成したAppSync APIにアクセスする内容となります。

対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。

2. nodeのインストール

こちらを参考

3. プロジェクトを作成

3-1-1. こちらを参考

4. 必要なライブラリをインストール

npm install aws-amplify

5. アプリと統合する

※前回作成したAPI画面の内容に従い、セットアップする

5-1. クライアントを接続

5-1-1. API設定のスニペットを使用して Amplify をセットアップ

import { Amplify } from 'aws-amplify';

Amplify.configure({
  API: {
    GraphQL: {
      endpoint: 'https://12345678901234567890123456.appsync-api.ap-northeast-1.amazonaws.com/graphql',
      region: 'ap-northeast-1',
      defaultAuthMode: 'apiKey',
      apiKey: 'ab2-abcdefghijklmnopqrstuvwxyz'
    }
  }
});

5-1-2. codegenを生成

npx @aws-amplify/cli codegen add --apiId 12345678901234567890123456 --region ap-northeast-1

※デフォルトでクライアントヘルパーコードが「src/graphql」フォルダに生成される。

※APIをデプロイするたびに、次のコマンドを再実行して、更新されたGraphQLステートメントとタイプを生成

npx @aws-amplify/cli codegen

6. ソースコード

6-1. 上記コマンド実行より自動生成

  • src
    • API.ts
    • graphql
      • mutations.ts
      • queries.ts
      • subscriptions.ts

6-2. クライアント側のソースコードを作成

6-2-1. src/app/appsync-client.ts

import { Amplify } from "aws-amplify";
import { generateClient } from "aws-amplify/api";

Amplify.configure({
  API: {
    GraphQL: {
      endpoint: 'https://12345678901234567890123456.appsync-api.ap-northeast-1.amazonaws.com/graphql',
      region: 'ap-northeast-1',
      defaultAuthMode: 'apiKey',
      apiKey: 'ab2-abcdefghijklmnopqrstuvwxyz'
    },
  },
});

const appsyncClient = generateClient();

export { appsyncClient };

6-2-2. src/app/app.css

body {
  margin: 0;
  background: linear-gradient(180deg, rgb(117, 81, 194), rgb(255, 255, 255));
  display: flex;
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  height: 100vh;
  width: 100vw;
  justify-content: center;
  align-items: center;
}

main {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

input {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  margin-right: 8px;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
  color: white;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

ul {
  padding-inline-start: 0;
  margin-block-start: 0;
  margin-block-end: 0;
  list-style-type: none;
  display: flex;
  flex-direction: column;
  margin: 8px 0;
  border: 1px solid black;
  gap: 1px;
  background-color: black;
  border-radius: 8px;
  overflow: auto;
}

li {
  background-color: white;
  padding: 8px;
}

li:hover {
  background: #dadbf9;
}

a {
  font-weight: 800;
  text-decoration: none;
}

6-2-3. src/app/page.tsx

"use client";

import {
  ChangeEvent,
  MouseEvent,
  KeyboardEvent,
  useState,
  useEffect,
} from "react";
import { appsyncClient } from "./appsync-client";
import { listPosts, getPost } from "../graphql/queries";
import { createPost, updatePost, deletePost } from "@/graphql/mutations";
import { onSubscribePost } from "@/graphql/subscriptions";
import { Post } from "@/API";
import "@/app/app.css";

export default function Home() {
  const [content, setContent] = useState<string>("");
  const [posts, setPosts] = useState<Post[]>();

  const getListPosts = async (limit: number) => {
    const { data, errors } = await appsyncClient.graphql({
      query: listPosts,
      variables: {
        limit,
      },
    });
    if (errors) console.error(`[ERROR]:${JSON.stringify(errors)}`);
    if (data) {
      const postData = data.listPosts?.posts as Array<Post>;
      postData.map((post) => {
        setPosts((prev) => [...(prev ?? []), post]);
      });
    }
  };

  useEffect(() => {
    getListPosts(10);
  }, []);

  const getPostData = async (id: string, e: MouseEvent<HTMLLIElement>) => {
    e.preventDefault();
    const { data, errors } = await appsyncClient.graphql({
      query: getPost,
      variables: {
        id,
      },
    });
    if (errors) console.error(`[ERROR]:${JSON.stringify(errors)}`);
  };

  const applyPost = async () => {
    if (content.length == 0) return;
    const { data, errors } = await appsyncClient.graphql({
      query: createPost,
      variables: {
        title: "My Post",
        content,
        author: "Sondon",
      },
    });
    if (errors) console.error(`[ERROR]:${JSON.stringify(errors)}`);
    setContent("");
  };

  const keyDownHandlerPost = async (e: KeyboardEvent<HTMLInputElement>) => {
    if (e.nativeEvent.isComposing || e.key !== "Enter") return;
    await applyPost();
  };

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    e.preventDefault();
    setContent(e.currentTarget.value);
  };

  const createPostData = async (e: MouseEvent<HTMLButtonElement>) => {
    e.preventDefault();
    await applyPost();
  };

  const updatePostData = async (
    id: string,
    author: string,
    expectedVersion: number,
    content: string,
    e: MouseEvent<HTMLButtonElement>
  ) => {
    e.preventDefault();
    const { data, errors } = await appsyncClient.graphql({
      query: updatePost,
      variables: {
        id,
        author,
        content,
        expectedVersion,
      },
    });
    if (errors) console.error(`[ERROR]:${JSON.stringify(errors)}`);
  };

  const deletePostData = async (
    id: string,
    e: MouseEvent<HTMLButtonElement>
  ) => {
    e.preventDefault();
    const { data, errors } = await appsyncClient.graphql({
      query: deletePost,
      variables: {
        id,
      },
    });
    if (errors) console.error(`[ERROR]:${JSON.stringify(errors)}`);
  };

  useEffect(() => {
    const sub = appsyncClient.graphql({ query: onSubscribePost }).subscribe({
      next: ({ data }) => {
        if (!data.onSubscribePost) return;
        const { mutationType, post } = data.onSubscribePost;
        if (post) {
          setPosts((prev) => {
            switch (mutationType) {
              case "CREATE":
                return [...(prev ?? []), post];
              case "UPDATE":
                return prev?.map((p) => (p.id === post.id ? post : p));
              case "DELETE":
                return prev?.filter((p) => p.id !== post.id);
            }
          });
        }
      },
      error: (error) => {
        console.error(`[ERROR] ${JSON.stringify(error)}`);
      },
    });
    return () => sub.unsubscribe();
  }, []);

  return (
    <div>
      <h2>My posts</h2>
      <input
        type="text"
        value={content}
        onChange={(e) => handleChange(e)}
        onKeyDown={keyDownHandlerPost}
        autoFocus={true}
      />
      <button onClick={(e) => createPostData(e)}>Register</button>
      <ul>
        {posts &&
          posts.map((post) => (
            <li key={post.id} onClick={(e) => getPostData(post.id, e)}>
              <div
                style={{
                  display: "flex",
                  flexWrap: "wrap",
                  overflow: "hidden",
                  justifyContent: "space-between",
                  alignItems: "center",
                }}
              >
                <div>{post.content}</div>
                <div>
                  <button onClick={(e) => deletePostData(post.id, e)}>
                    Delete
                  </button>
                </div>
              </div>
            </li>
          ))}
      </ul>
    </div>
  );
}

7. サーバーを起動

7-1-1. NextJSを立ち上げる

npm run dev

8. ブラウザで確認

  • http://localhost:3000

8-1-1. 画面

9. ディレクトリの構造

.
├── README.md
├── eslint.config.mjs
├── next-env.d.ts
├── next.config.ts
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
│   ├── file.svg
│   ├── globe.svg
│   ├── next.svg
│   ├── vercel.svg
│   └── window.svg
├── schema.json
├── src
│   ├── API.ts
│   ├── app
│   │   ├── app.css
│   │   ├── appsync-client.ts
│   │   ├── favicon.ico
│   │   ├── globals.css
│   │   ├── layout.tsx
│   │   └── page.tsx
│   └── graphql
│       ├── mutations.ts
│       ├── queries.ts
│       └── subscriptions.ts
└── tsconfig.json

4 directories, 24 files

10. 備考

今回は前回作成したAppSync APIにアクセスする内容についてでした。

11. 参考

投稿者プロフィール

Sondon
開発好きなシステムエンジニアです。
卓球にハマってます。

関連記事

  1. 【NextJS】UI Design using Stitch with…

  2. 【NextJS】Checkbox・Radio・Select

  3. 【NextJS】Online HTML Editor with Tip…

  4. 【NextJS】Pankoにオンラインビンゴが追加されました

  5. 【NextJS】Canvasを使い、図形を描画

  6. 【NextJS】DynamoDB with customType of…

最近の記事

制作実績一覧

  1. Checkeys