Language/React

React AWS Amplify DataStore Tutorial

conqueror-G 2022. 10. 10. 20:18

๋ชฉ์ฐจ

    ๐Ÿ“Œ DataStore with Amplify

    Amplify DataStore๋Š” ์˜คํ”„๋ผ์ธ ๋ฐ ์˜จ๋ผ์ธ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  ๊ณต์œ  ๋ฐ ๋ถ„์‚ฐ ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ชจ๋ธ์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ ๋ถ„์‚ฐํ˜• ๊ต์ฐจ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ์ปฌ ์ „์šฉ ๋ฐ์ดํ„ฐ๋กœ ์ž‘์—…ํ•˜๋Š” ๊ฒƒ๋งŒํผ ๊ฐ„๋‹จํ•˜๋‹ค.

    ๐Ÿ“Œ AWS ๊ณ„์ •*

    ๐Ÿ“Œ Amplify CLI ์„ค์น˜ ๋ฐ ๊ตฌ์„ฑ*

    Amplify CLI๋Š” ์•ฑ์šฉ AWS ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ†ตํ•ฉ ํˆด์ฒด์ธ์ด๋‹ค.

    ํ•˜๋‹จ์˜ ๋ฐฉ๋ฒ•์€ ๊ฐ€์ด๋“œ ์˜์ƒ์„ ๋ณด๊ณ  ์ง์ ‘ ์„ค๋ช…์„œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

    ๊ธ€๋กœ๋ฒŒ๋กœ ์ด๊ฒƒ์„ ์„ค์น˜ํ•œ๋‹ค.

    ๊ฒฝ๋กœ : ์ƒ๊ด€ ์—†์Œ

    ๋งŒ์•ฝ ์„ค์น˜๊ฐ€ ์•ˆ๋˜๋ฉด ์•ž์— ‘sudo’๋ฅผ ๋ถ™์ธ๋‹ค.

    npm install -g @aws-amplify/cli
    
    sudo npm install -g @aws-amplify/cli
    

    ๐Ÿ“Œ Amplify ํ™˜๊ฒฝ์„ค์ •

    ํ•œ๋ฒˆ๋งŒ ํ•˜๋ฉด๋˜๊ธฐ ๋•Œ๋ฌธ์—.. ๋งŒ์•ฝ ์ด์ „์— ํ•œ์ ์ด ์žˆ๋‹ค๋ฉด ๋„˜์–ด๊ฐ„๋‹ค.

    ๊ฒฝ๋กœ : ์ƒ๊ด€ ์—†์Œ

    amplify configure
    

    1. ์ž…๋ ฅํ•˜๋ฉด aws console์ฐฝ์ด ์›น ๋ธŒ๋ผ์šฐ์ €์— ๋กœ๋“œ๋˜๋Š”๋ฐ ๋กœ๊ทธ์ธ์„ ํ•˜๊ณ  ์—ญํ• ์„ ์ „ํ™˜ํ•œ๋‹ค.

    (ํšŒ์‚ฌ ๊ณ„์ •์ด ์•„๋‹ˆ๋ผ๋ฉด ์—ญํ•  ์ „ํ™˜ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค)

    2. ๋กœ๊ทธ์ธ์„ ํ•˜๊ณ  ํ„ฐ๋ฏธ๋„์ฐฝ์œผ๋กœ ๋‹ค์‹œ ๋Œ์•„์™€์„œ Enter๋ฅผ ๋ˆŒ๋Ÿฌ ์ด์–ด๊ฐ„๋‹ค.

    3. ์ง€์—ญ์„ ์„ ํƒํ•˜๋ผ๊ณ  ๋‚˜์˜จ๋‹ค. 

    - AWS ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์—ญ์„ ํ™•์ธํ•œ๋‹ค.

    - ์„ ํƒํ•œ๋‹ค

    4. user name์„ ์ž…๋ ฅํ•˜๋Š” ๋ฉ”์„ธ์ง€๊ฐ€ ์ถœ๋ ฅ๋˜๋Š”๋ฐ ์ด๋ฆ„์„ ์ž…๋ ฅํ•œ๋‹ค.

    - ์ž…๋ ฅํ•˜๊ฒŒ๋˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์— ์‚ฌ์šฉ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ํŽ˜์ด์ง€๊ฐ€ ์—ด๋ฆฌ๊ฒŒ๋œ๋‹ค.

     

    # example
    dave
    

     

    5. ์—ด๋ฆฐ ํŽ˜์ด์ง€์—์„œ ‘๋‹ค์Œ:๊ถŒํ•œ’ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•œ๋‹ค.

    6. ‘๋‹ค์Œ:ํƒœ๊ทธ’ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•œ๋‹ค.

    7. ‘๋‹ค์Œ:๊ฒ€ํ† ’ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•œ๋‹ค.

    8. ‘์‚ฌ์šฉ์ž ๋งŒ๋“ค๊ธฐ’ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•œ๋‹ค.

    9. ์•ก์„ธ์Šค ํ‚ค ID๋ฅผ ๋ณต์‚ฌํ•œ๋‹ค.

    10. ํ„ฐ๋ฏธ๋„ ์ฐฝ์œผ๋กœ ๋Œ์•„์˜จ๋‹ค.

    - Enterํ‚ค๋ฅผ ๋ˆŒ๋Ÿฌ ์ง„ํ–‰ํ•œ๋‹ค.

    11. accessKeyId๋ฅผ ์ž…๋ ฅํ•˜๋ผ๊ณ  ํ•œ๋‹ค 

    - ๋ณต์‚ฌํ•œ ID๋ฅผ ๋ถ™์—ฌ๋„ฃ๊ณ  Enter๋ฅผ ๋ˆ„๋ฅธ๋‹ค.

    12. secretAccessKey๋ฅผ ์ž…๋ ฅํ•˜๋ผ๊ณ  ๋‚˜์˜จ๋‹ค.

    - ๋‹ค์‹œ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋Œ์•„๊ฐ€์„œ ๋ณต์‚ฌํ•ด์„œ ๋Œ์•„์˜จ๋‹ค.

    - ํ‘œ์‹œ๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค.

    13. ๋ถ™์—ฌ๋„ฃ๊ธฐ๋ฅผ ํ•˜๊ณ  Enter๋ฅผ ๋ˆ„๋ฅธ๋‹ค.

    14. profile Name๋ฅผ ์ž…๋ ฅํ•˜๊ณ  enter๋ฅผ ๋ˆ„๋ฅด๋ฉด ์™„๋ฃŒ

    # example
    dave
    

    ๐Ÿ“Œ ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •

    1. Amplify ์ด๋‹ˆ์…œ๋ผ์ด์ฆˆ๋ฅผ ํ•œ๋‹ค.

    amplify init
    
    ? Enter a name for the project
    The following configuration will be applied:
    
    ?Project information
    | Name:  OJTFrontend
    | Environment: dev
    | Default editor: Visual Studio Code
    | App type: javascript
    | Javascript framework: react
    | Source Directory Path: src
    | Distribution Directory Path: build
    | Build Command: npm run-script build
    | Start Command: npm run-script start
    
    ? Initialize the project with the above configuration? Yes
    Using default provider  awscloudformation
    ? Select the authentication method you want to use: AWS profile
    
    ...
    
    ? Please choose the profile you want to use default
    

    2. Amplify ์•ฑ์„ ์ƒ์„ฑํ•œ๋‹ค.

    - ๊ฒฝ๋กœ : ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ† ๋ฆฌ

    - ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ํ•˜๋‹จ ๊ธ€์„ ์ฐธ๊ณ  ํ•œ๋‹ค.

    https://conqueror-g.tistory.com/238

    npx amplify-app
    

    3. ํ•ด๋‹น ํŒจํ‚ค์ง€๋“ค์„ ์„ค์น˜ํ•œ๋‹ค.

    npm i aws-amplify @aws-amplify/core @aws-amplify/datastore
    

    4. api ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผํ•œ๋‹ค.

    - ์ตœ์ดˆ 1๋ฒˆ๋งŒ ์ง€์ •ํ•˜๋ฉด ์ดํ›„์— ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

    amplify add api
    
    ? Please select from one of the below mentioned services:
        `GraphQL`
    ? Here is the GraphQL API that we will create. Select a setting to edit or continue:
        `Name`
    ? Provide API name:
        `BlogAppApi`
    ? Here is the GraphQL API that we will create. Select a setting to edit or continue:
        `Authorization modes: API key (default, expiration time: 7 days from now)`
    ? Choose the default authorization type for the API
        `API key`
    ? Enter a description for the API key:
        `BlogAPIKey`
    ? After how many days from now the API key should expire (1-365):
        `365`
    ? Configure additional auth types?
        `No`
    ? Here is the GraphQL API that we will create. Select a setting to edit or continue:
        `Conflict detection (required for DataStore): Disabled`
    ? Enable conflict detection?
        `Yes`
    ? Select the default resolution strategy
        `Auto Merge`
    ? Here is the GraphQL API that we will create. Select a setting to edit or continue:
        `Continue`
    ? Choose a schema template
        `Single object with fields (e.g., “Todo” with ID, name, description)`
    ? Do you want to edit the schema now? (Y/n)
        'Y'

    5. ํ•ด๋‹น ๊ฒฝ๋กœ์˜ ํŒŒ์ผ์—์„œ ์ธ์Šคํ„ด์Šค๋กœ ์ƒ์„ฑํ•  (ํƒ€์ž… ? ํ˜•ํƒœ?)๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. 

    - ๊ฒฝ๋กœ : /amplify/backend/api/schema.graphql

    type DiaryPost @model {
      id: ID!
      title: String!
      content: String!
    }
    
    ! : ํ•ญ์ƒ ๊ฐ’์„ ์ œ๊ณตํ•œ๋‹ค๋Š” ์˜๋ฏธ
    

    6. ๋ฐฑ์—”๋“œ๋ฅผ ๋ฐฐํฌํ•œ๋‹ค.

    amplify push
    
    ? Are you sure you want to continue? (Y/n)
    $ Y
    
    ? Do you want to generate code for your newly created GraphQL API (Y/n)
    (์ƒˆ๋กœ ๋งŒ๋“  GraphQL API์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?)
    $ n

    7. ํ•˜๊ธฐ ์ฝ”๋“œ๋ฅผ ํ„ฐ๋ฏธ๋„์— ์ž…๋ ฅํ•œ๋‹ค.

    - graphQL schma.grapql ๋‚ด์šฉ์„ ์ˆ˜์ •ํ•˜๋ฉด ๋‹ค์‹œ ํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค.

    amplify codegen model
    

    8. ํด๋ผ์ด์–ธํŠธ์—์„œ Amplify๋ฅผ ๊ตฌ์„ฑํ•˜์—ฌ ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค์™€ ์ƒํ˜ธ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.

    - ๊ฒฝ๋กœ : src/index.js

    import Amplify from '@aws-amplify/core'
    import config from './aws-exports'
    Amplify.configure(config)
    

    9. ํ•˜๊ธฐ ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์„œ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•œ๋‹ค.

    - ์ž์„ธํ•œ ์„ค๋ช…์€ ์˜ˆ์ œ ์ฝ”๋“œ ํ•˜๋‹จ์— ๋ง ๋ถ™์ธ๋‹ค.

    import React, { useState, useEffect } from 'react';
    import logo from './logo.svg';
    import './App.css';
    
    import { DataStore } from '@aws-amplify/datastore/lib-esm/datastore/datastore';
    import { DiaryPost } from './models';
    
    function App() {
      const [messages, updateMessages] = useState([]);
    
      useEffect(() => {
        fetchMessages();
        const subscription = DataStore.observe(DiaryPost).subscribe(() => {
          fetchMessages();
        });
    
        return () => subscription.unsubscribe();
      }, []);
    
      const fetchMessages = async () => {
        const messages = await DataStore.query(DiaryPost);
        updateMessages(messages);
      };
    
      const createMessage = async () => {
        await DataStore.save(
          new DiaryPost({
            ...{
              title: 'hi there~',
              content: 'nothing',
            },
          })
        );
      };
    
      const updateMessage = async () => {
        // ์ž„์‹œ๋กœ 0๋ฒˆ์จฐ ์ธ๋ฑ์Šค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๋„๋ก ํ•ด๋’€์Šต๋‹ˆ๋‹ค.
        const original = await DataStore.query(DiaryPost, messages[0].id);
        await DataStore.save(
          DiaryPost.copyOf(original, updated => {
            // eslint-disable-next-line no-unused-expressions
            (updated.title = 'good'), (updated.content = 'content');
          })
        );
      };
    
      const deleteMessage = async () => {
        // ์ž„์‹œ๋กœ 0๋ฒˆ์จฐ ์ธ๋ฑ์Šค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜๋„๋ก ํ•ด๋’€์Šต๋‹ˆ๋‹ค.
        const deleteMessage = await DataStore.query(DiaryPost, messages[0].id);
        await DataStore.delete(deleteMessage);
      };
    
      return (
        <div className='App'>
          <header className='App-header'>
            <img src={logo} className='App-logo' alt='logo' />
            <div>
              <input type='button' value='NEW' onClick={createMessage} />
              <input type='button' value='UPDATE' onClick={updateMessage} />
              <input type='button' value='DELETE' onClick={deleteMessage} />
            </div>
            {messages.map(data => {
              return (
                <div key={data.id}>
                  <div>{data.title}</div>
                  <div>{data.content}</div>
                </div>
              );
            })}
          </header>
        </div>
      );
    }
    
    export default App;
    

    ์ถ”๊ฐ€ ์„ค๋ช…

    - ๋ฐ์ดํ„ฐ์Šคํ† ์–ด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋ง ํ•ด์ฃผ๋Š” ์ฝ”๋“œ ์ค‘์š”ํ•œ์ง€ ๋ชฐ๋ž์œผ๋‚˜,
    ์ด ์ฝ”๋“œ๊ฐ€ ์—†์œผ๋ฉด ๋ฐ์ดํ„ฐ์Šคํ† ์–ด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ญ์ƒ ๋ฐ˜์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    ๋ฆฌ์•กํŠธ์˜ ๋ Œ๋”๋ง๊ณผ๋Š” ๋ณ„๊ฐœ๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.
    ๋ฐ์ดํ„ฐ์Šคํ† ์–ด์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋ ๋•Œ๋งˆ๋‹ค fetch๋ฅผ ํ•œ๋‹ค.
    useEffect์˜ ์˜์กด์„ฑ ๋ฐฐ์—ด์ด ๋นˆ์นธ์ธ ๊ฒƒ๊ณผ๋Š” ๊ด€๊ณ„๊ฐ€ ์—†๋‹ค.

    useEffect(() => {
        fetchMessages();
    		// fetchMessages๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” query์ด๋‹ค.
        const subscription = DataStore.observe(DiaryPost).subscribe(() => {
          fetchMessages();
        });
    
        return () => subscription.unsubscribe();
      }, []);
    
    const fetchMessages = async () => {
        const messages = await DataStore.query(DiaryPost);
        updateMessages(messages);
      };
    

    - ๋ฐ์ดํ„ฐ์Šคํ† ์–ด์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.
    ์ด๊ฒƒ์— ๋Œ€ํ•ด์„œ๋Š” ํฌ๊ฒŒ ์„ค๋ช…ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.
    ’DiaryPost’๋Š” graphql ๋ชจ๋ธ ์ด๋ฆ„์ด๋‹ค.

    const createMessage = async () => {
        await DataStore.save(
          new DiaryPost({
            ...{
              title: 'hi there~',
              content: 'nothing',
            },
          })
        );
      };
    

    - ๋ฐ์ดํ„ฐ์Šคํ† ์–ด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.
    ๋ชจ๋ธ๊ณผ ๋ฐ”๊พธ๊ณ ์žํ•˜๋Š” ๋ฐ์ดํ„ฐ์˜ id๋ฅผ original์ด๋ผ๋Š” ๋ณ€์ˆ˜์— ๋‹ด๊ณ , ๋ฐ”๊พธ๊ณ ์ž ํ•˜๋Š” ๋‚ด์šฉ์„ ์ „์†กํ•œ๋‹ค.

    const updateMessage = async () => {
        // ์ž„์‹œ๋กœ 0๋ฒˆ์จฐ ์ธ๋ฑ์Šค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๋„๋ก ํ•ด๋’€๋‹ค.
        const original = await DataStore.query(DiaryPost, messages[0].id);
        await DataStore.save(
          DiaryPost.copyOf(original, updated => {
            // eslint-disable-next-line no-unused-expressions
            (updated.title = 'good'), (updated.content = 'content');
          })
        );
      };
    

    - ๋ฐ์ดํ„ฐ์Šคํ† ์–ด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•œ๋‹ค. ์‚ญ์ œํ•˜๊ณ ์ž ํ•˜๋Š” id๋ฅผ ๋ณด๋‚ด๋ฉด ์‚ญ์ œ๊ฐ€ ์™„๋ฃŒ๋œ๋‹ค.

    const deleteMessage = async () => {
        // ์ž„์‹œ๋กœ 0๋ฒˆ์จฐ ์ธ๋ฑ์Šค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜๋„๋ก ํ•ด๋’€์Šต๋‹ˆ๋‹ค.
        const deleteMessage = await DataStore.query(DiaryPost, messages[0].id);
        await DataStore.delete(deleteMessage);
      };ใ…
    

     

     

     

     

     

    ์ด๋ ‡๊ฒŒ ๋ฐฑ์•ค๋“œ ๊ฐœ๋ฐœ์ž๋ผ๋Š” ๋™๋ฃŒ ์—†์ด CRUD์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋น„์Šค๋ฅผ ์—ฐ๊ฒฐ์‹œ์ผœ๋ณด์•˜๋‹ค.

    Amplify DataStore ๋„Œ.. ์ •๋ง ๊ฐ•์ ์ด์—ˆ์–ด.. 3์ผ์„ ํ•ด๋งธ๋‹ค