Post

Graph QL(4) - Create, Read

๐Ÿ“Œ์‹œ์ž‘ํ•˜๋ฉฐ

์•ž์—์„œ Graph QL์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ›‘์–ด๋ดค๋Š”๋ฐโ€ฆ ๊ทธ๋Ÿผ REST API์—์„œ ๋งํ•˜๋Š” CRUD๋Š” ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”๊ฑฐ์ง€?๐Ÿค” ๋ผ๋Š” ์˜๋ฌธ์ด ๋“ค์—ˆ๊ณ  ๊ฐ„๋‹จํ•œ ์–ดํ”Œ์„ ๋งŒ๋“ค์–ด ๋ณด๋ฉด์„œ ๊ณต๋ถ€ํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜๊ณ ์žํ•œ๋‹ค.

(์ •ํ™•ํžˆ ๋งํ•˜๋ฉด CRUD๋Š” ์•„๋‹ˆ์ง€๋งŒ ์ด ๊ธ€์—์„œ๋Š” ํŽธ์˜์„ฑ์„ ์œ„ํ•ด CRUD๋ผ๊ณ  ์ž‘์„ฑํ•œ๋‹ค)

โœ…์ „์ฒด ํ๋ฆ„

์ด CRUD๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํฌ๊ฒŒ ์„ธ ๋ถ€๋ถ„์„ ์ž‘์„ฑํ•˜๊ฒŒ ๋œ๋‹ค. (์ž์„ธํ•œ ๋‚ด์šฉ์€ ์ด ํฌ์ŠคํŒ… ์ฐธ์กฐ)

๐Ÿ“Œ๋ฐฑ์—”๋“œ

  • typeDefs

  • resolvers

๐Ÿ“Œํ”„๋ก ํŠธ์—”๋“œ

  • useQuery

  • useMutation

โœ…Read

์ฝ๋Š” ๋ถ€๋ถ„์€ type Query๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋•Œ, Query ๋‚ด๋ถ€์— ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ type๋„ ํ•จ๊ป˜ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์— ์ฃผ์˜ํ•œ๋‹ค!

์•„๋ž˜ ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉด, type Book๊ณผ type Movie์„ ์ง€์ •ํ•˜๊ณ  type Query์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// server.js
const typeDefs = gql`
  type Book {
    id: Int
    title: String
    author: String
  }

  type Movie {
    id: Int
    title: String
    director: String
    release: Int
  }

  type Query {
    books: [Book]
    movies: [Movie]
  }
`

Apollo Server๊ฐ€ ์ด Query๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ถˆ๋Ÿฌ์˜ฌ์ง€ resolvers์— ํ• ๋‹นํ•ด ์ค€๋‹ค. books์™€ movies๋Š” ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด ๋‘” ๋ฐ์ดํ„ฐ๋ฅผ ํ• ๋‹นํ•ด ์ฃผ์—ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const books = [
  {
    id: 1,
    title: "ํ•ด๋ฆฌํฌํ„ฐ",
    author: "JK ๋กค๋ง",
  },
  {
    id: 2,
    title: "๊ทธ๋ฆฌ๊ณ  ์•„๋ฌด๋„ ์—†์—ˆ๋‹ค",
    author: "์• ๊ฑฐ์„œ ํฌ๋ฆฌ์Šคํ‹ฐ",
  },
]

const movies = [
  {
    id: 1,
    title: "์ˆ˜์ˆ˜๊ป˜๋ผ! ๊ฝƒํ”ผ๋Š” ์ฒœํ•˜๋–ก์žŽํ•™๊ต",
    director: "ํƒ€์นดํ•˜์‹œ ์™€ํƒ€๋ฃจ",
    release: 2022,
  },
  {
    id: 2,
    title: "๋ฒ”์ฃ„๋„์‹œ4",
    director: "ํ—ˆ๋ช…ํ–‰",
    release: 2024,
  },
]

const resolvers = {
  Query: {
    books: () => books,
    movies: () => movies,
  },
}

๋ฐฑ์—”๋“œ์—์„œ์˜ ์ž‘์—…์€ ๋งˆ๋ฌด๋ฆฌ ๋˜์—ˆ์œผ๋‹ˆ, ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ด ๋ฐ์ดํ„ฐ๋“ค์„ ์ฝ์–ด๋ณด์ž.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { gql, useQuery } from "@apollo/client"

export const BOOKS = gql`
  query {
    books {
      title
      author
      id
    }
  }
`

function Read() {
  const { loading, error, data } = useQuery(BOOKS)

  if (loading) return <p>๋กœ๋”ฉ์ค‘!</p>
  if (error) return <p>์—๋Ÿฌ๋ฐœ์ƒ!</p>
  return (
    <>
      <h2>๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ(Get)</h2>
      {data.books.map(
        ({
          title,
          author,
          id,
        }: {
          title: string
          author: string
          id: number
        }) => (
          <div key={title} className="book">
            <p>
              {author} : {title}
            </p>
          </div>
        )
      )}
    </>
  )
}

export default Read
  1. @apollo/client ์—์„œ gql์™€ useQuery๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  2. BOOKS ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑ

    • ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ฑ…๊ณผ ์˜ํ™” ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ GraphQL ์ฟผ๋ฆฌ
  3. useQuery ํ›… ์‚ฌ์šฉ

    • GraphQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. ์ด๋•Œ loading, error, data ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , ๋กœ๋”ฉ ์ค‘์ด๋ฉด โ€œ๋กœ๋”ฉ์ค‘!โ€์„, ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด โ€œ์—๋Ÿฌ๋ฐœ์ƒ!โ€์„ ํ™”๋ฉด์— ํ‘œ์‹œํ•œ๋‹ค.
  4. ๋ฐ์ดํ„ฐ map
    • data.books.map์„ ์‚ฌ์šฉํ•ด ๊ฐ ์ฑ…์˜ ์ •๋ณด๋ฅผ ๋ฐ˜๋ณตํ•˜๊ณ  ํ™”๋ฉด์— ์ถœ๋ ฅํ•œ๋‹ค.

โœ…Create

Create๋ถ€ํ„ฐ๋Š” type Query๊ฐ€ ์•„๋‹ˆ๋ผ type Mutation์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ž‘์„ฑ ํ๋ฆ„ ์ž์ฒด๋Š” ์œ„์™€ ๊ต‰์žฅํžˆ ๋น„์Šทํ•˜๋‹ค!

๋จผ์ € typeDefs๋ฅผ ์ด์šฉํ•ด GraphQL ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•˜์ž. ์˜ˆ์ œ์—์„œ๋Š” Book ํƒ€์ž…๊ณผ addBook ๋ฎคํ…Œ์ด์…˜(Mutation)์„ ์ •์˜ํ–ˆ๋‹ค. ์ด๋•Œ type Mutation์˜ ๊ฒฝ์šฐ Book ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
const typeDefs = gql`
  type Book {
    id: Int
    title: String
    author: String
  }

  type Mutation {
    addBook(id: Int, title: String, author: String): Book
  }
`

๋‹ค์Œ์€ resolvers๋ฅผ ํ†ตํ•ด GraphQL ์„œ๋ฒ„๊ฐ€ ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•ด์ค€๋‹ค. addBook๋ฆฌ์กธ๋ฒ„ ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜ _๋Š” ๋ถ€๋ชจ ๊ฐ์ฒด๋กœ, ์—ฌ๊ธฐ์„œ๋Š” ์‚ฌ์šฉ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์–ธ๋”์Šค์ฝ”์–ด๋กœ ํ‘œ์‹œํ•œ๋‹ค.
  • ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜ { title, author }๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฎคํ…Œ์ด์…˜์„ ์š”์ฒญํ•  ๋•Œ ์ „๋‹ฌํ•˜๋Š” ์ธ์ˆ˜๋‹ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const books = [
  {
    id: 1,
    title: "ํ•ด๋ฆฌํฌํ„ฐ",
    author: "JK ๋กค๋ง",
  },
  {
    id: 2,
    title: "๊ทธ๋ฆฌ๊ณ  ์•„๋ฌด๋„ ์—†์—ˆ๋‹ค",
    author: "์• ๊ฑฐ์„œ ํฌ๋ฆฌ์Šคํ‹ฐ",
  },
]

let nextBookId = 3

const resolvers = {
  Mutation: {
    addBook: (_, { title, author }) => {
      const newBook = { id: nextBookId++, title, author }
      books.push(newBook)
      return newBook
    },
  },
}

addBook ํ•จ์ˆ˜๋Š” ์œ„์—์„œ ๋ฏธ๋ฆฌ ์ •์˜ํ•ด ๋‘” books ๋ฐฐ์—ด์— newBook์ด๋ž€ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , newBook์„ returnํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ return ํ•œ ๊ฐ’์€ ํด๋ผ์ด์–ธํŠธ์ธก์—์„œ `data.addBook`์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค!

์ด์ œ ํ”„๋ก ํŠธ์—”๋“œ ์ธก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

  1. GraphQL ์ฟผ๋ฆฌ(ADD_BOOK)์„ ์ž‘์„ฑํ•œ๋‹ค.
  2. mutation AddBook($title: String, $author: String)์„ ์ด์šฉํ•ด AddBook์ด๋ผ๋Š” Mutation์„ ์ •์˜ํ•œ๋‹ค. ์ด Mutation์€ title๊ณผ author๋ผ๋Š” ๋‘ ๊ฐœ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›๋Š”๋‹ค. addBook Mutation์˜ ๊ฒฐ๊ณผ๋กœ id, title, author๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.
1
2
3
4
5
6
7
8
9
10
11
12
import { useMutation, gql } from "@apollo/client"
import { useEffect, useState } from "react"

const ADD_BOOK = gql`
  mutation AddBook($title: String, $author: String) {
    addBook(title: $title, author: $author) {
      id
      title
      author
    }
  }
`
  1. useMutation ํ›…์„ ์‚ฌ์šฉํ•ด ์œ„์—์„œ ์ •์˜ํ•œ ADD_BOOK GraphQL Mutation์„ ํ˜ธ์ถœํ•œ๋‹ค. ์ด๋–„ refetchQueries ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด Mutation์ด ์‹คํ–‰๋œ ํ›„ ์ž๋™์œผ๋กœ BOOKS ์ฟผ๋ฆฌ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค. ์ด ์„ค์ •์ด ์—†๋‹ค๋ฉด, ์—…๋ฐ์ดํŠธ ํ•ด๋„ ์ด๊ฒƒ์ด ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋‹ค.

  2. useState ํ›…์„ ์‚ฌ์šฉํ•ด ๋‚ด์šฉ์„ ์—…๋ฐ์ดํŠธ ํ•˜๋„๋ก ํ•œ๋‹ค.

5.loading์ด true์ธ ๊ฒฝ์šฐ โ€œ์ถ”๊ฐ€ ์ค‘โ€ฆโ€ ํ…์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•œ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
export default function Create() {
  const [addBook, { data, loading, error }] = useMutation(ADD_BOOK, {
    refetchQueries: [{ query: BOOKS }],
  })
  const [title, setTitle] = useState<string>("")
  const [author, setAuthor] = useState<string>("")

  useEffect(() => {
    if (error) {
      console.error("์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.:", error)
    }
    if (data) {
      console.log("์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.:", data.addBook)
    }
  }, [data, error])

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        if (title && author) {
          addBook({
            variables: { title, author },
          }).catch((e) => {
            console.error("์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–‡์Šต๋‹ˆ๋‹ค:", e)
          })
          setTitle("")
          setAuthor("")
        }
      }}
    >
      <input
        value={author}
        onChange={(e) => setAuthor(e.target.value)}
        placeholder="์ž‘๊ฐ€"
      />
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="์ฑ… ์ œ๋ชฉ"
      />
      <button type="submit" disabled={loading}>
        ์ถ”๊ฐ€ํ•˜๊ธฐ
      </button>
      {loading && <p>์ถ”๊ฐ€ ์ค‘...</p>}
    </form>
  )
}

๐Ÿ—‚๏ธ์ฐธ๊ณ  ์‚ฌ์ดํŠธ

This post is licensed under CC BY 4.0 by the author.