카테고리 없음

[GraphQL] #1. Apollo Server

bas96 2021. 8. 9. 13:11

지난 번 REST API를 공부하면서 GraghQL이란 무엇인지 짧게 알아볼 수 있었는데 효율적인 방법이라고 생각이 들었고,

많은 회사에서 GraphQL을 사용하는 것을 알게 되었습니다.

 

그래서 이번에는 직접 쿼리를 짜보고 통신이 어떻게 이루어지는지 알아보기로 하였습니다~! 🤗

 

GraphQL 통신 구현을 위한 셋팅, GraphQL Server 생성, GraphQL Client 생성 이렇게 3개로 나누어 포스팅 할 예정입니다.

 

이번에는 GraphQL에 대해 소개와 Server 셋팅을 하고, 간단하게Query와 Mutation을 구현해보도록 하겠습니다.

그 전에 그래프큐엘 공식문서를 한번 보시고 오면 더 이해할 수 있습니다! 

길이가 길지 않고 직접 예시 쿼리를 작성해 볼 수도 있기때문에 연습하기에도 매우 편합니다.👏🏻굿굿

 

GraphQL Server는 mySQL, mongodb등을 사용하는 대신에 기능이 작동할 수 있도록 함수를 제작하여 완벽한 서버의 형태가 아님을 말씀드립니다.

프론트엔드 개발자로서 GraphQL Server가 어떤 방식으로 구현되는지 이해할 수 있는 정도로 기본적인 기능만 구현되었음을 참고해주세요~:)


1. GraphQL 강점

GraphQL의 강점을 REST API와 비교했을 때, 상대적으로

 

# 1. 필요한 정보들만 선택하여 받아올 수 있어서 Overfetching 문제를 해결할 수 있습니다.

이는 불필요한 데이터 전송량을 감소시킬 수 있습니다.

# 2. 여러 계층의 정보들을 한 번에 받아올 수 있기에 Underfetching 문제를 해결할 수 있습니다.

# 3. 하나의 endPoint에서 모든 요청을 처리합니다.

Rest API 통신을 할 때는 원하는 정보를 가지고 올 때마다 URI를 작성해야 했지만 GraphQL은 하나의 URI에서 모든 요청이 가능합니다..

2. Apollo란? && Apollo를 사용한 이유?

GraphQL은 REST API 처럼 '형식'입니다. 

따라서 이 형식을 구현해 볼 수 있는 솔루션이 필요합니다.

Postman이 REST API 통신을 구현해준다면, Apollo는 GraphQL 프로젝트의 명령어를 실행해볼 수 있는 곳입니다!

 

GraphQL을 구현할 수 있는 솔루션은 매우 다양합니다. 

하지만 그 중에 아폴로를 선택한 이유는 1. Backend, Frontend를 모두 제공하고, 2. 간편하고 쉬우면서, 3. 많은 사용 횟수를 가졌기 때문입니다.

3.  GraphQL Server셋팅

npm init: package.json만들기, 

npm install -g nodemon: (nodemon이란? 코드가 바뀔 때 마다 노드제이에스가 인식해서 바로 실행해주는 것.)

nodemon index.js: Apollo 로컬호스트 4000으로 연결되면서 데이터 전송 구조 확인

(프로젝트 실행 명령어 -> package.json에서 script를 start로 바꿔서 npm명령어인 npm start로 진행되게 함)

 

(csv 파일은 얄팍한 코딩사전의 강의코드를 사용하였습니다.)

- 데이터는 csv 형식으로 저장합니다.

- npm inatall convert-csv-to-json 설치하고 npm start하면 파일들 안에 있는 data가 모두 json형식으로 출력됩니다.

- index.js: csv파일로 된 데이터들이 활용될 수 있도록 합니다.

- dbreater.js (없어도 됨. 그냥 간단하게 데이터 볼 수 있도록 콘솔에서 확인하려고 만듦)

 

npm i apollo-sever

ApolloServer는 typeDefresolver를 인자로 받아서 서버를 생성합니다.

단어에 대한 간단한 설명만 한 후, 직접 query와 mutation을 구현하면 개념에 대해 더 이해하시기 쉽습니다.

 

typeDefs

  • GraphQL 명세에서 사용될 데이터로, 요청의 타입을 지정합니다.
  • gql(template literal tag)로 생성됩니다.
  • 데이터의 구조가 선언되는 곳입니다.

resolver

  • 서비스의 액션들을 함수로 지정합니다.
  • 요청에 따라 데이터를 반환, 입력, 수정, 삭제합니다.

GraphQL Playground

  • 작성한 GraphQL type, resolver 명세를 확인하고
  • 데이터 요청과 전송을 테스트합니다.

1. Query로 데이터 가져오기

1-1 overfetching 극복

쿼리를 사용하여 데이터를 가져오는 작업을 해볼까요?

 

GraphQL의 장점 중 하나는 Overfetching을 하지않고 원하는 data만 가져올 수 있는 점이었습니다.

아래는 index.js파일의 typeDefs의 일부입니다.

teams는 Team을 배열로 가져오고, Team은 다양한 인자들을 가집니다.

type Query {
    teams: [Team]
  }
type Team {
    id: Int
    manager: String
    office: String
    extension_number: String
    mascot: String
    cleaning_duty: String
    project: String
  }

 

그런데 우리가 만약 각 Team의 매니저와 오피스 정보만 필요하다면 이 정보들을 모두 가져올 필요가 없습니다.

 

아래와 같이 필요한 데이터만 선택하여 가져올 수 있습니다. 

이것이 Overfetching을 하지 않아 불필요한 데이터 통신을 줄일 수 있습니다.

또한 조건을 주어서 특정 팀만 받아올 수도 있습니다. 

항목 이름을 teamdmfh 바꾸고, 인자로 id를 지정해서 원하는 4팀의 정보만 가져와 보겠습니다.

 

그러기 위해서는 백엔드에서 조건을 제공해줄 루트쿼리를 지정해주어야 합니다.

위에 작성한 teams밑에 id라는 인자가 들어가고, 하나의 팀(단수)을 받는다라는 gql을 작성해줍니다.

typeDefs

그리고 resolver에 team쿼리를 추가해 줘야합니다.

resolver

그럼 아래와 같이 인자를 주고 4팀에 대한 정보만 가져올 수 있답니다~

1-2 underfetching 극복

자, overfetching 해결 예시를 보았으니 이제 underfetching을 해결할 수 있는 예시를 볼까요?

바로 한번에 여러 계층을 받아오는 것입니다.

team과 팀이 가지고 있는 supplies를 함께 보고싶습니다.

type Query {
    teams: [Team]
    team(id: Int): Team
    equipments: [Equipment]
    supplies: [Supply] 👈 supplies에 다수의 Supply가 반환된다~
  }
type Team {
    id: Int
    manager: String
    office: String
    extension_number: String
    mascot: String
    cleaning_duty: String
    project: String
    supplies: [Supply] 👈
  }

 

supplies를 가져오는 resolver 추가

그랬더니 한 번에 4팀의 supplies를 함께 받아올 수 있었습니다.

 

원하는 데이터만 골라서, 또 더 원하는 다른 데이터를 함께 가져옴 으로써 효율적, 선택적으로 데이터를 가지고 올 수 있었습니다.

 

그럼 이제는 데이터를 삭제, 추가, 수정 할 수 있는 Mutation을 어떻게 요청하는지보겠습니당!


2. Mutation 

2-1. 삭제

typeDefs 의 typeDefs 밑에 type Mutation의 루트타입을 만들어 주었습니다.

그리고 물품 하나를 지우는 뮤테이션 하나를 지정해 주었습니다.

어떤 것을 지우는지 알아야하니까 인자를 하나 받아주고, 어떤 것을 반환해줄지 써줍니다.

type Query {
    teams: [Team]
    team(id: Int): Team
    equipments: [Equipment]
    supplies: [Supply]
  }
type Mutation {
	deletEquipment(id: String): Equipment 👈 ✅ 뮤테이션타입 만들기
}
type Team {
    id: Int
    manager: String
    office: String
    extension_number: String
    mascot: String
    cleaning_duty: String
    project: String
    supplies: [Supply]
  }

deletEquipment 를 resolver에 만들어줍니다.

=> query에서 오른쪽 response가 나오는 것을 확인하였습니다. 

mutation에 deletEquipment함수를 실핼하여 지우고싶은 equipment의 id를 넣어줍니다.

 

저희는 'pen tablet'을 없애보겠습니다.

=> pen tablet을 인자로 넣어 mutation에 돌리니 오른쪽에 지우게 될 equipment인 pen tablet이 반환되었습니다!

 

그럼 다시 쿼리를 돌려볼까요?

=> 짠~! pen tablet 항목이 사라졌습니다.

 

# 2-2. 추가

이제 새로운 물품을 추가해주는 것을 해보겠습니다.

 

데이터를 추가 할 때에는, 해당 데이터에서 항목들의 내용이 들어가야 하기 때문에 인자로 보내주어야 하는 항목들이 많습니다!

deletEquipment에는 id한개만 인자로 받았는데, 둘을 비교해보았을 때 인자가 정말 많죠?

인자가 많음!

insertEquipment도 resolver를 만들어 준 뒤 4000번 포트에서 돌려주면 새로 들어온 equipment가 반환되는 것을 알 수 있습니다.

resolver - insertEquipment 추가 (수정기능 스포당해버림.)

=> 새로 들어온 equipment가 반환되었음

=> 다시 쿼리를 이용해 equipments를 받아보면? 마지막에 제가 추가한 laptop이 들어온 것을 볼 수있습니다.

 

 

# 2-3. 수정

자 그럼 수정하는 것도 이해가 잘 되시겠쥬?

같은 방식으로 root type을 만들어주고, resolver를 추가해주면 됩니다.

editEquipment 
mutation에 editEquipment 추가

=> pen tablet을 새거로 count를 10개에서 111개로, 디자이너에서 developer가 사용하는 것으로 바꾸어 보겠습니다.

=> 완성!

(음.. 근데 삭제, 추가 할 때는 어떤 equipment가 반환되는지 보여줬는데 저렇게 null이 뜨더라구요?

근데 query를 돌리면 수정이 제대로 됩니다..

다행히 수정은 잘 된거지만 왜 반환되는 값이 저렇게 나오는지 아시는 분이 있으시다면 설명해주면 감사드리겠습니다..!)