Amplify Gen2で作成したAppSync APIをReactから呼び出し、基本的なCRUD処理を実装しよう!

technologies

  • HOME
  • BLOG
  • technologies
  • Amplify Gen2で作成したAppSync APIをReactから呼び出し、基本的なCRUD処理を実装しよう!

おはようございます。

ベンジャミンの木村と申します!

今回これまでAmplify Gen2でAppSyncを構築する手順を説明してきましたが、その続きとして、「アプリケーションで、このAppSyncをどう使うの?」というところについて焦点をあてて、説明していきたいと思います。

この記事では、ReactからAppSyncのAPIを呼び出し、以下のCRUD機能を実装することで、具体的な利用方法を学んでいきます。

この機能を作成するための、AWSの構成としては下記のようになります。

また、最終的にReact側の画面が下記の通りとなります

  • CRUD機能の内容
    • Add new MenuItemボタンを押すとDBにレコード作成する(Create)
    • DBのレコードを読み出し、画面に表示する(Read)
    • Edit MenuItemボタンを押すとレコードの内容を編集する(Update)
    • Delete MenuItemを押すことで、DBのレコード削除する(Delete)

それでは早速、次のセクションから手順を説明していきます。

目次

1. バックエンド(AppSync/DynamoDB)の準備

こちらは以前作成した記事があるので、参考に作成してください

2. React側の準備

Amplify Gen2では、ReactからAppSync APIを呼び出すための公式ライブラリが提供されています。まずは、そのライブラリをインストールして設定を行いましょう。

2-1. ターミナルでAmplify クライアント ライブラリをプロジェクトにインストール

npm add aws-amplify

2-2. アプリのエントリ ポイント (src/main.tsx)で、次の編集を行う

変更前

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'





createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

変更後

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { Amplify } from 'aws-amplify';
import outputs from '../amplify_outputs.json';

Amplify.configure(outputs);

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

2-3. src/App.tsxのReactの初期設定を削除しておく

※不要なReactの初期設定は削除し、App.tsxの中身は下記の状態になるようにしておいてください。(後にMenuItemコンポーネントを追加します)

import './App.css'

function App() {
  return (
    <>
    </>
  )
}

export default App

2-4. src配下に、下記に通りMenuItem.tsxファイルを作成する

※MenuItem.tsxはAPIを呼び出し、CRUD機能を提供するコンポーネントです。

.
├── amplify
│   ├── backend.ts
│   └── data
│       └── resource.ts
├── src
│   ├── App.tsx
│   ├── main.tsx
│   └── MenuItem.tsx
├── amplify_outputs.json
├── package.json
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── eslint.config.js

2-5. MenuItem.tsxでAppSync APIを呼び出すための初期設定を行う

import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

export function MenuItem() {
 
}

上記コードについては下記にて解説を行います。

  • Schemaのインポート:
    • AppSyncで定義した、スキーマの型をインストールできます。Schema型を使用することによって、AppSyncとReactで型のズレをなくすような役割を持ちます。
  • generateClientのインポート:
    • AWS Amplify の data モジュールからインポートされている関数です。この関数は、AppSyncのデータ操作用のクライアントを生成します。
  • client の生成:
    • const client = generateClient<Schema>();
    • 生成されたclientを使用して、AppSync APIを通じてデータの取得、編集、更新、削除を実行できるようになります。

MenuItem 関数コンポーネントに具体的な処理は記載しておりません。次のセクションよりこの中に、具体的な処理を記載し、実装していきます。

2-6. MenuItemコンポーネントをApp.tsxに追加する

変更前


import './App.css'

function App() {
  return (
    <>

    </>
  )
}

export default App

変更後

import { MenuItem } from './MenuItem'
import './App.css'

function App() {
  return (
    <>
      <MenuItem />
    </>
  )
}

export default App

3. AppSyncのデータの取得(Read)

3-1. データの取得処理の実装

MenuItem関数コンポーネントの中に下記の通り実装を追加してください

変更前


import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

export function MenuItem() {




























}

変更後

import { useState, useEffect } from "react";
import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

export function MenuItem() {
  const [menuItems, setMenuItems] = useState<Schema["MenuItem"]["type"][]>([]);

  const fetchMenuItems = async () => {
    const { data: items } = await client.models.MenuItem.list();
    setMenuItems(items);
  };

  useEffect(() => {
    fetchMenuItems();
  }, []);

  return (
    <div>
      {menuItems.map((item) => (
        <div key={item.id}>
          <h2>{item.category}</h2>
          <p>今日のおすすめは:{item.name}</p>
          <p>説明:{item.description}</p>
          <p>価格:{item.price}</p>
          <p>{item.isVegetarian ? "Vegetarian Menu" : "Non-vegetarian Menu"}</p>
          <p>ID:{item.id}</p>
        </div>
      ))}
    </div>
  );
}

追記したコードについて下記にて説明を行います。

※なお、userState、useEffectの詳細な説明は省略します。

  • 状態の定義 (useState):
    • const [menuItems, setMenuItems] = useState<Schema["MenuItem"]["type"][]>([]);
    • こちらでDBに保存されているMenuItemデータの一覧の状態管理をしています。
    • 型 (Schema["MenuItem"]["type"][]):
      • AppSyncのSchemaで定義している型はSchema["<スキーマ名>"]["type"]で取得可能です。今回[{DBデータ1},{DBデータ2}]とデータが入るので、末尾に [] が記載されています。
  • データ取得の関数 (fetchMenuItems):
    • const fetchMenuItems = async () => {
        const { data: items } = await client.models.MenuItem.list();
        setMenuItems(items);
      };
    • クライアントの models.MenuItem に定義された list メソッドを非同期で呼び出しています。
    • client.models.MenuItem.list();の中のデータは下記の通りになっています。
      • data: [
          {
            category: "メインディッシュ",
            createdAt: "2024-12-13T13:15:59.400Z",
            description: "トマトソースとバジル、モッツァレラチーズのピザ",
            id: "72a5a8e7-f4ca-46d5-ac1a-2869919c7d33",
            isVegetarian: true,
            name: "マルゲリータピザ",
            price: 1200,
            updatedAt: "2024-12-13T13:15:59.400Z"
          }
        ]
  • 副作用フック (useEffect):
    • useEffect(() => {
        fetchMenuItems();
      }, []);
    • 画面が初期レンダリングされる時に、fetchMenuItemsを呼び出し、データの取得を行えるように設定しています。

これら処理から取得したデータをreturn(<div>〜<div>)内でHTMLとして表示設定しています。

3-2. 下記コマンドを実行し、表示されるか確かめる

npx ampx sandbox
npm run dev

※コマンド実行前にaws configureでローカルのAWS CLIとAWSアカウントを接続できる状態にしておいてください。

実行すると下記URLが表示されるので、ブラウザで入力をする。

http://localhost:5173/

下記のように表示されればOKです!

4. AppSyncのデータの作成処理(Create)

4-1. データの作成処理の実装

次にデータを作成処理を実装していきます。MenuItem関数コンポーネントの中に下記の通り実装を追加してください

変更前

import { useState, useEffect } from "react";
import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

export function MenuItem() {
  const [menuItems, setMenuItems] = useState<Schema["MenuItem"]["type"][]>([]);
  
  const fetchMenuItems = async () => {
    const test = await client.models.MenuItem.list();
    const { data: items } = await client.models.MenuItem.list();
    console.log(test);
    setMenuItems(items);
  };

  useEffect(() => {
    fetchMenuItems();
  }, []);












  return (
    <div>
      {menuItems.map((item) => (
        <div key={item.id}>
          <h2>{item.category}</h2>
          <p>今日のおすすめは:{item.name}</p>
          <p>説明:{item.description}</p>
          <p>価格:{item.price}</p>
          <p>{item.isVegetarian ? "Vegetarian Menu" : "Non-vegetarian Menu"}</p>
          <p>ID:{item.id}</p>
        </div>
      ))}

  );
}

変更後

import { useState, useEffect } from "react";
import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

export function MenuItem() {
  const [menuItems, setMenuItems] = useState<Schema["MenuItem"]["type"][]>([]);
  
  const fetchMenuItems = async () => {
    const test = await client.models.MenuItem.list();
    const { data: items } = await client.models.MenuItem.list();
    console.log(test);
    setMenuItems(items);
  };

  useEffect(() => {
    fetchMenuItems();
  }, []);

  const addMenuItem = async () => {
    await client.models.MenuItem.create({
      name: "New Item",
      description: "New description",
      category: "New category",
      price: 0,
      isVegetarian: false,
    })
  }

  return (
    <div>
      {menuItems.map((item) => (
        <div key={item.id}>
          <h2>{item.category}</h2>
          <p>今日のおすすめは:{item.name}</p>
          <p>説明:{item.description}</p>
          <p>価格:{item.price}</p>
          <p>{item.isVegetarian ? "Vegetarian Menu" : "Non-vegetarian Menu"}</p>
          <p>ID:{item.id}</p>
        </div>
      ))}
      <button onClick={addMenuItem}>Add new MenuItem</button>
  );
}

追記したコードについて下記にて説明を行います。

  • データの作成関数 (addMenuItem):
    • client.models.MenuItem.create
      • クライアント(client)を介して、メニューアイテムのデータをデータベースに送信します。
      • メソッド (create):
        • データベースに新しいアイテムを作成(POSTリクエストに相当)。
    • 引数として渡すオブジェクト:
      • name: "New Item",
        description: "New description",
        category: "New category",
        price: 0,
        isVegetarian: false,
      • 新しく作成するメニューアイテムのデータを指定います。DBにはこちらの値が追加されます。

4-2. 実装した処理の確認

ここまで実装すると、項番3-2で表示した「Add new MenuItem」ボタンが追加されていると思います。一度こちらをクリックしてみてください。

あれ、何も起こらないけど…」と感じると思います。

次にDynamoDBのデータを見ていきます。

確認手順としては、「作成したAppSyncの画面」→左ペインより「データソース」→ソースの「MenuItem-◯◯」→画面右上の「テーブルアイテムの探索」より表示できます。

DBにはデータがしっかり入っています。

「Add new MenuItem」ボタンを押した時に、なぜ画面はなぜ変わらなかったのかというと、「Add new MenuItem」ボタンを押した時に起こる処理としては、「データの作成処理」のみです。

ボタンを押したタイミングで再度、「データの取得処理」を関数に入れてやらないと、ボタンを押したタイミングで、画面にすぐに表示されないのです。

そのため以下の通りデータの取得処理を関数に追加してください。

4-3. データ作成処理関数にデータ取得処理機能を追加

変更前

〜〜〜〜〜〜〜
  const addMenuItem = async () => {
    await client.models.MenuItem.create({
      name: "New Item",
      description: "New description",
      category: "New category",
      price: 0,
      isVegetarian: false,
    })

  }
〜〜〜〜〜〜〜

変更後

〜〜〜〜〜〜〜
  const addMenuItem = async () => {
    await client.models.MenuItem.create({
      name: "New Item",
      description: "New description",
      category: "New category",
      price: 0,
      isVegetarian: false,
    })
    await fetchMenuItems();
  }
〜〜〜〜〜〜〜

4-4. 実装した処理の確認

これで再度「Add new MenuItem」ボタンを押すと、下記のようにすぐに、その追加されたデータが画面に表示されるようになります。

※項番4-2でボタンを押した直後は画面に何も変更は起こりませんが、画面のリロードが実施されると、useEffect(() => {fetchMenuItems();}, []);が起動し、New categoryのデータが画面に追加されます。ですので、変更前にもNew categoryが入っているのはそのためです。

変更前

変更後

次のセクションからは各関数にfetchMenuItems()を加えたまま実装していきます。

5. AppSyncのデータの編集処理(Update)

5-1. データの編集処理の実装

次にデータの編集処理を実装していきます。MenuItem関数コンポーネントの中に下記の通り実装を追加してください。

※後に解説にも記載しておりますが、<編集したいデータのID>の部分は、自身の用途に合わせて、変更してください。

変更前

import { useState, useEffect } from "react";
import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

export function MenuItem() {
  const [menuItems, setMenuItems] = useState<Schema["MenuItem"]["type"][]>([]);
  
  const fetchMenuItems = async () => {
    const test = await client.models.MenuItem.list();
    const { data: items } = await client.models.MenuItem.list();
    console.log(test);
    setMenuItems(items);
  };

  useEffect(() => {
    fetchMenuItems();
  }, []);

  const addMenuItem = async () => {
    await client.models.MenuItem.create({
      name: "New Item",
      description: "New description",
      category: "New category",
      price: 0,
      isVegetarian: false,
    })
    await fetchMenuItems();
  }












  return (
    <div>
      {menuItems.map((item) => (
        <div key={item.id}>
          <h2>{item.category}</h2>
          <p>今日のおすすめは:{item.name}</p>
          <p>説明:{item.description}</p>
          <p>価格:{item.price}</p>
          <p>{item.isVegetarian ? "Vegetarian Menu" : "Non-vegetarian Menu"}</p>
          <p>ID:{item.id}</p>
        </div>
      ))}
      <button onClick={addMenuItem}>Add new MenuItem</button>
      <button onClick={editMenuItem}>Edit MenuItem</button>
    </div>
  );
}

変更後

import { useState, useEffect } from "react";
import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

export function MenuItem() {
  const [menuItems, setMenuItems] = useState<Schema["MenuItem"]["type"][]>([]);
  
  const fetchMenuItems = async () => {
    const test = await client.models.MenuItem.list();
    const { data: items } = await client.models.MenuItem.list();
    console.log(test);
    setMenuItems(items);
  };

  useEffect(() => {
    fetchMenuItems();
  }, []);

  const addMenuItem = async () => {
    await client.models.MenuItem.create({
      name: "New Item",
      description: "New description",
      category: "New category",
      price: 0,
      isVegetarian: false,
    })
    await fetchMenuItems();
  }
  const editMenuItem = async () => {
    await client.models.MenuItem.update({
      id: "<編集したいデータのID>",
      name: "Updated Item",
      description: "Updated description",
      category: "Updated category",
      price: 0,
      isVegetarian: false,
    })
    await fetchMenuItems();
  }

  return (
    <div>
      {menuItems.map((item) => (
        <div key={item.id}>
          <h2>{item.category}</h2>
          <p>今日のおすすめは:{item.name}</p>
          <p>説明:{item.description}</p>
          <p>価格:{item.price}</p>
          <p>{item.isVegetarian ? "Vegetarian Menu" : "Non-vegetarian Menu"}</p>
          <p>ID:{item.id}</p>
        </div>
      ))}
      <button onClick={addMenuItem}>Add new MenuItem</button>
      <button onClick={editMenuItem}>Edit MenuItem</button>
    </div>
  );
}

追記したコードについて下記にて説明を行います。

  • データの編集関数 (addMenuItem):
    • client.models.MenuItem.update
      • クライアント(client)を介して、メニューアイテムのデータをデータベースに送信します。
      • メソッド (update):
        • データベース上の既存のアイテムを更新(PUTまたはPATCHリクエストに相当)。
    • 引数として渡すオブジェクト:
      • id: "<編集したいデータのID>",
        name: "Updated Item",
        description: "Updated description",
        category: "Updated category",
        price: 0,
        isVegetarian: false,
      • 更新するメニューアイテムのデータを指定します。
        ※”<編集したいデータのID>”に記載する値は以下の画面の赤枠の「ID:◯◯」◯◯の部分を記載ください。こちらがDynamoDBに保存している主キーの値になります。

5-2. 実装した処理の確認

実装された「Edit MenuItem」ボタンをクリックしてください。項目通り、値が変更されるのがわかります。

変更前

変更後

6. AppSyncのデータの削除処理(Delete)

6-1. データの削除処理の実装

次にデータの削除処理を実装していきます。MenuItem関数コンポーネントの中に下記の通り実装を追加してください。

※先ほどの編集処理でも記載しておりますが、<削除したいデータのID>の部分は、自身の用途に合わせて、変更してください。

変更前

import { useState, useEffect } from "react";
import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

export function MenuItem() {
  const [menuItems, setMenuItems] = useState<Schema["MenuItem"]["type"][]>([]);
  
  const fetchMenuItems = async () => {
    const test = await client.models.MenuItem.list();
    const { data: items } = await client.models.MenuItem.list();
    console.log(test);
    setMenuItems(items);
  };

  useEffect(() => {
    fetchMenuItems();
  }, []);

  const addMenuItem = async () => {
    await client.models.MenuItem.create({
      name: "New Item",
      description: "New description",
      category: "New category",
      price: 0,
      isVegetarian: false,
    })
    await fetchMenuItems();
  }
  const editMenuItem = async () => {
    await client.models.MenuItem.update({
      id: "f4cc9eba-1dbe-425d-a350-aceb9e8a4ab4",
      name: "Updated Item",
      description: "Updated description",
      category: "Updated category",
      price: 0,
      isVegetarian: false,
    })
    await fetchMenuItems();
  }
  const deleteMenuItem = async () => {
    await client.models.MenuItem.delete({
      id: "d5d135db-78e8-440b-a3a9-8150e442e4f4"
    })
    await fetchMenuItems();
  }

  return (
    <div>
      {menuItems.map((item) => (
        <div key={item.id}>
          <h2>{item.category}</h2>
          <p>今日のおすすめは:{item.name}</p>
          <p>説明:{item.description}</p>
          <p>価格:{item.price}</p>
          <p>{item.isVegetarian ? "Vegetarian Menu" : "Non-vegetarian Menu"}</p>
          <p>ID:{item.id}</p>
        </div>
      ))}
      <button onClick={addMenuItem}>Add new MenuItem</button>
      <button onClick={editMenuItem}>Edit MenuItem</button>
      <button onClick={deleteMenuItem}>Delete MenuItem</button>
    </div>
  );
}

変更後

import { useState, useEffect } from "react";
import type { Schema } from "../amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

export function MenuItem() {
  const [menuItems, setMenuItems] = useState<Schema["MenuItem"]["type"][]>([]);
  
  const fetchMenuItems = async () => {
    const test = await client.models.MenuItem.list();
    const { data: items } = await client.models.MenuItem.list();
    console.log(test);
    setMenuItems(items);
  };

  useEffect(() => {
    fetchMenuItems();
  }, []);

  const addMenuItem = async () => {
    await client.models.MenuItem.create({
      name: "New Item",
      description: "New description",
      category: "New category",
      price: 0,
      isVegetarian: false,
    })
    await fetchMenuItems();
  }
  const editMenuItem = async () => {
    await client.models.MenuItem.update({
      id: "<編集したいデータのID>",
      name: "Updated Item",
      description: "Updated description",
      category: "Updated category",
      price: 0,
      isVegetarian: false,
    })
    await fetchMenuItems();
  }
  const deleteMenuItem = async () => {
    await client.models.MenuItem.delete({
      id: "<削除したいデータのID>"
    })
    await fetchMenuItems();
  }

  return (
    <div>
      {menuItems.map((item) => (
        <div key={item.id}>
          <h2>{item.category}</h2>
          <p>今日のおすすめは:{item.name}</p>
          <p>説明:{item.description}</p>
          <p>価格:{item.price}</p>
          <p>{item.isVegetarian ? "Vegetarian Menu" : "Non-vegetarian Menu"}</p>
          <p>ID:{item.id}</p>
        </div>
      ))}
      <button onClick={addMenuItem}>Add new MenuItem</button>
      <button onClick={editMenuItem}>Edit MenuItem</button>
      <button onClick={deleteMenuItem}>Delete MenuItem</button>
    </div>
  );
}

追記したコードについて下記にて説明を行います。

  • データの削除関数 (deleteMenuItem):
    • client.models.MenuItem.delete
      • クライアント(client)を介して、メニューアイテムのデータをデータベースに送信します。
      • メソッド (update):
        • データベース上の既存アイテムを削除(DELETEリクエストに相当)。
    • 引数として渡すオブジェクト:
      • id: "<削除したいデータのID>"
      • 削除するメニューアイテムのデータを指定します。
        ※”<削除したいデータのID>”に記載する値は編集処理と同様に画面に表示されている「ID:◯◯」◯◯の部分を記載ください。こちらがDynamoDBに保存している主キーの値になります。

6-2. 実装した処理の確認

実装された「Delete MenuItem」ボタンをクリックしてください。選択したIDのデータが削除されるのがわかります。

変更前

変更後

まとめ

いかがでしたでしょうか?

今回はReactからAmplify Gen2で作成したAppSync APIを呼び出す方法について記載しました。Amplify Gen2でAppSyncを用いれば、フロントエンド、バックエンドの実装がより簡単にできることをご理解いただけたのではないでしょうか?

今回は簡易的な実装でしたが、こちらを応用することにより、実案件でも活かしていただけますと幸いです。

Related posts