GraphQL React + Apollo Tutorial - 4. Mutation: 링크 생성하기

cadenzah·2020년 3월 4일
2
post-thumbnail

뮤테이션: 링크 생성하기

이번 장에서는 Apollo를 사용하여 뮤테이션을 전송하는 방법을 배웁니다. 쿼리를 전송하는 것과 크게 다르지 않고 똑같이 세 단계를 거치는데, 약간의 (하지만 논리적인) 차이점이 마지막 두 단계에 존재합니다.

  1. gql 파서 함수를 사용하여 자바스크립트 상수로 뮤테이션을 작성합니다.

  2. <Mutation /> 컴포넌트에 GraphQL 뮤테이션과 (필요한 경우) 변수를 props로 전달하여 사용합니다.

  3. 컴포넌트의 Render Props 함수에 주입된 뮤테이션 함수를 사용합니다.

React 컴포넌트 준비하기

이전과 마찬가지로, 사용자가 새로운 링크를 추가할 수 있는 React 컴포넌트를 우선 만들어봅시다.

src/components 디렉토리에 CreateLink.js라는 새로운 파일을 만들고 아래의 코드를 입력합니다.
($ .../hackernews-react-apollo/src/components/CreateLink.js)

import React, { Component } from 'react'

class CreateLink extends Component {
  state = {
    description: '',
    url: '',
  }

  render() {
    const { description, url } = this.state
    return (
      <div>
        <div className="flex flex-column mt3">
          <input
            className="mb2"
            value={description}
            onChange={e => this.setState({ description: e.target.value })}
            type="text"
            placeholder="A description for the link"
          />
          <input
            className="mb2"
            value={url}
            onChange={e => this.setState({ url: e.target.value })}
            type="text"
            placeholder="The URL for the link"
          />
        </div>
        <button onClick={`...🔜 이 부분을 구현할 겁니다 `}>Submit</button>
      </div>
    )
  }
}

export default CreateLink

사용자가 만들고자 하는 Linkurldescription을 제공할 수 있는 두 개의 input 필드를 가진, 아주 전형적인 React 컴포넌트 구성입니다. 이 필드에 입력되는 데이터는 컴포넌트의 state에 저장되고, 뮤테이션이 전송될 때에 사용될 겁니다.

뮤테이션 작성하기

하지만 서버에 뮤테이션을 어떻게 전송할까요? 이전 장에서 사용했던 3단계를 그대로 따라가도록 하겠습니다.

우선, 자바스크립트 코드로 뮤테이션을 정의하고, 컴포넌트를 graphql 컨테이너로 감싸야 합니다. 쿼리를 다룰 때에 했던 것과 비슷한 작업입니다.

CreateLink.js 파일에 아래와 같은 코드를 추가합니다.
($ .../hackernews-react-apollo/src/components/CreateLink.js)

const POST_MUTATION = gql`
  mutation PostMutation($description: String!, $url: String!) {
    post(description: $description, url: $url) {
      id
      createdAt
      url
      description
    }
  }
`

또한, button 요소를 아래의 코드로 대체합니다.
($ .../hackernews-react-apollo/src/components/CreateLink.js)

<Mutation mutation={POST_MUTATION} variables={{ description, url }}>
  {() => (
    <button onClick={`...🔜 이 부분을 구현할 겁니다 `}>
      Submit
    </button>
  )}
</Mutation>

위 코드를 한번 살펴보겠습니다.

  1. 우선, POST_MUTATION이라는 자바스크립트 상수를 만들어 뮤테이션을 저장합니다.

  2. 이제, POST_MUTATIONprops로 전달하는 <Mutation /> 컴포넌트의 Render Prop 함수로 button 요소를 감쌉니다.

  3. 마지막으로 descriptionurlvariables이라는 이름의 props로 전달합니다.

다음으로 진행하기 전에, Apollo 의존성을 불러와야 합니다. 아래 코드를 CreateLink.js 파일의 최상단에 추가합니다.
($ .../hackernews-react-apollo/src/components/CreateLink.js)

import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'

이제, 뮤테이션이 작동하도록 코드를 만들어봅시다!

CreateLink.js 파일에서 <Mutation /> 컴포넌트를 아래의 내용으로 대체합니다.
($ .../hackernews-react-apollo/src/components/CreateLink.js)

<Mutation mutation={POST_MUTATION} variables={{ description, url }}>
  {postMutation => <button onClick={postMutation}>Submit</button>}
</Mutation>

앞에서 말씀드렸듯이, 단지 <Mutation /> 컴포넌트의 Render Props 함수를 통하여 주입된 함수 postMutation을 버튼의 onClick 내부에서 실행하도록 하기만 하면 됩니다.

뮤테이션이 제대로 작동하는지 확인해봅시다. 코드를 테스트할 수 있도록, App.js 파일을 열고 render 함수를 아래와 같이 수정합니다.
($ .../hackernews-react-apollo/src/components/App.js)

render() {
  return <CreateLink />
}

다음으로, App.js 파일의 최상단에 아래의 코드를 추가하여 CreateLink 컴포넌트를 불러옵니다.
($ .../hackernews-react-apollo/src/components/App.js)

import CreateLink from './CreateLink'

이제 yarn start를 실행하면, 아래와 같은 화면이 보일 겁니다.

두 개의 input 필드와 submit 버튼입니다. 썩 이쁘지는 않지만, 그래도 제 기능은 다 합니다.

필드에 데이터를 적어보겠습니다.

  • Description: The best learning resource for GraphQL
  • URL: www.howtographql.com

그리고 subimt 버튼을 눌러보세요. UI가 바로 바뀌지는 않겠지만, 그래도 뮤테이션이 제대로 이루어졌는지 확인해보도록 하죠. Playground에서 현재 링크의 리스트를 확인해보겠습니다.

브라우저에서 http://localhost:4000에 접속하면 Playground를 열 수 있습니다. 거기서 아래의 쿼리를 전송합니다.

# Try to write your query here
{
  feed {
    links {
      description
      url
    }
  }
}

서버로부터 다음과 같은 응답을 받을 수 있을 겁니다.

{
  "data": {
    "feed": {
      "links": [
        // ...
        {
          "description": "The best learning resource for GraphQL",
          "url": "www.howtographql.com"
        }
      ]
    }
  }
}

아주 좋아요! 뮤테이션이 잘 작동하는군요. 💪

역자 부록: 인증 토큰 없이 post 뮤테이션 실행시키기

지금부터는 본 튜토리얼을 따라가는 과정에서 발생하는 오류를 해결하기 위한 방편을 역자가 임의로 추가한 내용입니다.

이번 프로젝트에서 사용하는 post 뮤테이션을 사용하면, 사용자가 Hackersnews 서비스에 원하는 링크와 설명을 추가할 수 있게 됩니다. 그런데, 해당 뮤테이션을 현재 단계에서 사용하려고 하면 인증 문제가 발생할 겁니다.

// `post` 뮤테이션 실행시 반환되는 JSON 데이터
{
  "data": null,
  "errors": [
    {
      "message": "Not authenticated",
      "locations": [
        {
          "line":2,
          "column":3
        }
      ],
      "path": [
        "post"
      ]
    }
  ]
}

별도의 설정을 따로 하지 않으셨다면, 위와 같은 오류가 발생하는 것이 정상입니다. post 뮤테이션은 Node.js Resolver에서 요청을 보낸 사용자를 식별한 뒤, 해당 사용자 정보를 링크에 저장하도록 구현되어있기 때문입니다.

($ .../hackernews-node/src/Mutation.js)

function post(parent, args, context, info) {
  const userId = getUserId(context)

  const newLink = context.prisma.link.create({
    data: {
      url: args.url,
      description: args.description,
      postedBy: { connect: { id: userId } },
    }
  })
  context.pubsub.publish("NEW_LINK", newLink)

  return newLink
}

여기서 사용되는 getUserId() 메서드가 요청 헤더에 포함된 사용자 토큰을 토대로 현재 사용자의 userId를 추출 및 활용하도록 준비하며, 만약 토큰이 헤더 상에 없다면 오류를 발생시키게 됩니다.

($ .../hackernews-node/src/utils.js)

function getUserId(context) {
  const Authorization = context.request.get('Authorization')
  if (Authorization) {
    const token = Authorization.replace('Bearer ', '')
    const { userId } = jwt.verify(token, APP_SECRET)
    return userId
  }

  throw new Error('Not authenticated')
}

따라서 아직 요청 헤더 상에 토큰을 포함시키는 로직이 구현되지 않은 현재 단계에서는, 토큰 검증 로직을 제외시킨 뒤에 GraphQL 서비스를 실행시키면 문제 없이 post 뮤테이션이 실행될 것입니다. 아래와 같이 코드를 수정한 뒤 서버를 실행시키고, 다시 React 프로젝트에서 뮤테이션을 실행해보세요.

function post(parent, args, context, info) {
  // const userId = getUserId(context)

  const newLink = context.prisma.link.create({
    data: {
      url: args.url,
      description: args.description,
      // postedBy: { connect: { id: userId } },
    }
  })
  context.pubsub.publish("NEW_LINK", newLink)

  return newLink
}

Quiz

다음 중 올바른 설명은?

  • 오직 쿼리만 graphql 고차 컴포넌트로 감쌀 수 있다.
  • <Mutation /> 컴포넌트는 varibale, optimisticResponse, refetchQueries, update 등을 props로 전달받는다.
  • graphql을 사용하여 컴포넌트를 뮤테이션을 감쌀 때, Apollo는 Render Props 함수 내로 뮤테이션 함수만 주입한다.
  • GraphQL 뮤테이션은 인자를 일절 받지 않는다.

2개의 댓글

comment-user-thumbnail
2020년 6월 14일

Mutation을 전송할 때 token 값이 헤더에 포함되지 않아서 Not Authorize 에러가 발생하는데 토큰을 별도로 포함하지 않아도 되야하는 것이 맞나요?? 물론 로그인 구현해서 로컬스토리지에 토큰 저장해놓고 헤더에 포함해주면 해결될 문제인것 같긴한데, 지금 이 단계에서 토큰을 포함하지 않고 정상적으로 뮤테이션이 되어야 하는지 궁금합니다!! 포스트 너무 잘봤습니다 ㅎㅎ :)

1개의 답글