GraphQL과 Apollo 다섯번째 이야기 - GraphQL을 간단하게 구현해 보아요 😀

2023. 11. 19. 21:38Back-End 작업실/기타 참고 자료

728x90
반응형

 

 

카카오페이 | 마음 놓고 금융하다

여기를 눌러 링크를 확인하세요.

qr.kakaopay.com

 

 

 

GraphQL과 타입스크립트로 개발하는 웹 서비스:설계부터 개발·배포까지 따라 하며 완성하는 웹 풀

COUPANG

www.coupang.com

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

 




🗂 목차

✅ GraphQL과 Apollo 첫번째 이야기 - 개념 익히기
 GraphQL과 Apollo 두번째 이야기 - REST API란?
 GraphQL과 Apollo 세번째 이야기 - GraphQL의 정보 주고 받는 방식

 GraphQL과 Apollo 네번째 이야기 - Apollo란?
✅ GraphQL과 Apollo 다섯번째 이야기 - GraphQL을 간단하게 구현해 보아요 😀
 GraphQL과 Apollo 여섯번째 이야기 - GraphQL Module화에 대해 알아보아요 😀
 GraphQL과 Apollo 일곱번째 이야기 - GraphQL Data Type에 대해 알아보아요 😀
 GraphQL과 Apollo 여덟번째 이야기 - GraphQL Union과 Interface 그리고 인자와 인풋 타입에 대해 알아보아요 😀
 GraphQL과 Apollo 아홉번째 이야기 - Java + Spring Boot에서 GraphQL 사용해 보기 - 실습 환경 구성
 GraphQL과 Apollo 열번째 이야기 - Java + Spring Boot에서 GraphQL 사용해 보기 - 실습 해보기
✅ GraphQL과 Apollo 열 한번째 이야기 - TypeScript + Nest.js에서 GraphQL 사용해 보기 - 실습 환경 구성
✅ 
GraphQL과 Apollo 열 두번째 이야기 - TypeScript + Nest.js에서 GraphQL 사용해 보기 - 실습 환경 테스트
 GraphQL과 Apollo 열 세번째 이야기 - TypeScript + Nest에서 GraphQL 사용해 보기 - 실습 해보기
 
GraphQL과 Apollo 열 네번째 이야기 - React와 Apollo Client
 GraphQL과 Apollo 열다섯번째 이야기 - React와 Apollo Client - Query와 Mutation 사용하여 웹 페이지 만들기
 GraphQL과 Apollo 열 여섯번째 이야기 - Kotlin + Spring Boot에서 GraphQL 사용해 보기 - 실습 환경 구성
 GraphQL과 Apollo 열 일곱번째 이야기 - Kotlin + Spring Boot에서 GraphQL 사용해 보기 - 실습 해보기


🤔 내가 만난 문제

⚠️ [Nest.js] TypeORM Table 관계가 맺어졌을 때, Seeding (feat. Migration)
⚠️ [Spring Boot 3.0] Could not resolve org.springframework.boot:spring-boot-gradle-plugin
⚠️ [Spring Boot 3] Spring Doc(Swagger) White Label Error


📋 부록

🔍 [Nest.js] 초기 환경 구성 (feat. TypeORM, QueryBuilder, GraphQL, Apollo)
🔍 [SOLID][Nest.js][Java + Spring] Interface를 활용한 결합도 분리 (Interface를 이용한 Dependency Injection - DI)

 

 

 

🚀 GraphQL과 Apollo 다섯번째 이야기

    🔽 GraphQL을 간단하게 구현해 보아요 😀

        📦 개요

1. type 해부!

지난 글에서 주니는 GraphQLApollo 실습을 위한 Node.js 프로젝트를 만들어 보았어요.
해당 프로젝트로 이야기를 이어나가 보도록 할게요.

index.js


index.js에 보면 4 ~ 6번째 줄에 Query 라는 type이 지정 되어 있는 걸 확인할 수 있어요.
Query란 Type 안에 어떤 요청들이 들어오고, 그것으로 어떤 형식의 데이터들이 반환될지 지정되어 있는 것이에요.

그래서 클라이언트에서 Teams라는 Query를 요청하게 되면 5번째 줄에 대괄호안에 Team이 있는데, 이 Team이란 데이터가 복수로 여러개 반환된다는 의미에요.

index.js


즉, 위의 Team이라는 Type의 데이터가 여러개 반환될거야! 라는 의미에요.
정리해보자면 클라이언트에서 teams라는 Query를 요청하면 Team이라는 객체 데이터가 여러개 반환된다!가 되겠네요.


index.js


위 부분을 GraphQL에서는 Query Root Type이라고 부르고 있어요.
Query Root Type은 자료 요청에 사용될 Query들을 정의하는 것이고, Query 명령문마다 반환될 데이터 형식을 지정하는 것이에요.

 

 

 

 

 

 

2. Resolver 해부!

index.js


Resolver는 위와 같이 코딩해 주었는데, Query라는 것은 Object(객체)의 항목들로 데이터를 반환하는 함수 선언이에요.
실제 프로젝트로 예를 들면 DBMS에서 Database 조회를 위한 Code 등이 될 수 있어요.

즉, Resolver는 Query로 진행하는 작업들이 실질적으로 이뤄지는 코드에요.
19번째 줄에 teams 항목은 화살표 함수 형식으로 데이터베이스 안에 있는 teams라는 데이터를 반환하라고 이뤄진 것을 볼 수 있어요.

 

 

 

 

    🔽 실습!

        📦 equipments를 반환하는 Query를 만들어 보아요 😀

최초 dbtester.js를 만들어 줄거에요.

dbtester.js


위와 같이 프로젝트 최상위 디렉터리 밑에 dbtester.js를 만들어 주었어요.


dbtester.js


일단, database 안에 있는 모든 내용을 출력해 보기 위해 위와 같이 수정해 보았어요.


npm install


그런 뒤 위 명령어를 통해 Module을 추가 설치 해 주었어요.

nodemon dbtester.js

 JunySS 🐵   ~/Programming/study/apolloStudy  nodemon dbtester.js
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node dbtester.js`
{
  teams: [
    {
      id: 1,
      manager: 'Mandy Warren',
      office: '101A',
      extension_number: '#5709',
      mascot: 'Panda',
      cleaning_duty: 'Monday',
      project: 'Hyperion'
    },
    {
      id: 2,
      manager: 'Stewart Grant',
      office: '101B',
      extension_number: '#4012',
      mascot: 'Tadpole',
      cleaning_duty: 'Tuesday',
      project: 'Zen'
    },
    {
      id: 3,
      manager: 'Smantha Wheatly',
      office: '102A',
      extension_number: '#3852',
      mascot: 'Falcon',
      cleaning_duty: 'Wednesday',
      project: 'Duranno'
    },
    {
      id: 4,
      manager: 'Francis Buckley',
      office: '103B',
      extension_number: '#1039',
      mascot: 'Beaver',
      cleaning_duty: 'Thursday',
      project: 'Genghis'
    },
    {
      id: 5,
      manager: 'Blake Smith',
      office: '104A',
      extension_number: '#7750',
      mascot: 'Wildcat',
      cleaning_duty: 'Friday',
      project: 'Acheron'
    }
  ],
  people: [
    {
      id: 1,
      first_name: 'Alex',
      last_name: 'Davidson',
      sex: 'male',
      blood_type: 'O',
      serve_years: 2,
      role: 'developer',
      team: 2,
      from: 'California'
    },
    {
      id: 2,
      first_name: 'Lindsay',
      last_name: 'West',
      sex: 'female',
      blood_type: 'A',
      serve_years: 3,
      role: 'designer',
      team: 3,
      from: 'Arizona'
    },
    {
      id: 3,
      first_name: 'Nathan',
      last_name: 'Jenkins',
      sex: 'male',
      blood_type: 'B',
      serve_years: 1,
      role: 'planner',
      team: 1,
      from: 'Texas'
    },
    {
      id: 4,
      first_name: 'Christine',
      last_name: 'Harris',
      sex: 'female',
      blood_type: 'B',
      serve_years: 2,
      role: 'developer',
      team: 4,
      from: 'Ohio'
    },
    {
      id: 5,
      first_name: 'Page',
      last_name: 'Adams',
      sex: 'female',
      blood_type: 'O',
      serve_years: 5,
      role: 'developer',
      team: 2,
      from: 'California'
    },
    {
      id: 6,
      first_name: 'Herbert',
      last_name: 'Ford',
      sex: 'male',
      blood_type: 'A',
      serve_years: 2,
      role: 'designer',
      team: 5,
      from: 'Messachusetts'
    },
    {
      id: 7,
      first_name: 'Dennis',
      last_name: 'Marshall',
      sex: 'male',
      blood_type: 'AB',
      serve_years: 1,
      role: 'planner',
      team: 2,
      from: 'Colorado'
    },
    {
      id: 8,
      first_name: 'Isabella',
      last_name: 'Martin',
      sex: 'female',
      blood_type: 'A',
      serve_years: 3,
      role: 'developer',
      team: 1,
      from: 'Georgia'
    },
    {
      id: 9,
      first_name: 'Alfred',
      last_name: 'Clarke',
      sex: 'male',
      blood_type: 'AB',
      serve_years: 4,
      role: 'designer',
      team: 4,
      from: 'Pennsylvania'
    },
    {
      id: 10,
      first_name: 'Tyler',
      last_name: 'Philips',
      sex: 'male',
      blood_type: 'O',
      serve_years: 1,
      role: 'designer',
      team: 5,
      from: 'Virginia'
    },
    {
      id: 11,
      first_name: 'Noelle',
      last_name: 'Holmes',
      sex: 'female',
      blood_type: 'AB',
      serve_years: 5,
      role: 'planner',
      team: 3,
      from: 'California'
    },
    {
      id: 12,
      first_name: 'Kate',
      last_name: 'Owen',
      sex: 'female',
      blood_type: 'B',
      serve_years: 2,
      role: 'developer',
      team: 1,
      from: 'Maine'
    },
    {
      id: 13,
      first_name: 'Priscilla',
      last_name: 'Walker',
      sex: 'female',
      blood_type: 'O',
      serve_years: 2,
      role: 'developer',
      team: 4,
      from: 'California'
    },
    {
      id: 14,
      first_name: 'Shirley',
      last_name: 'Grant',
      sex: 'female',
      blood_type: 'AB',
      serve_years: 1,
      role: 'developer',
      team: 3,
      from: 'Messachusetts'
    },
    {
      id: 15,
      first_name: 'Byron',
      last_name: 'Barnes',
      sex: 'male',
      blood_type: 'A',
      serve_years: 3,
      role: 'designer',
      team: 1,
      from: 'Idaho'
    },
    {
      id: 16,
      first_name: 'Toby',
      last_name: 'Lewis',
      sex: 'male',
      blood_type: 'A',
      serve_years: 1,
      role: 'planner',
      team: 3,
      from: 'New York'
    },
    {
      id: 17,
      first_name: 'Barbara',
      last_name: 'White',
      sex: 'female',
      blood_type: 'AB',
      serve_years: 4,
      role: 'developer',
      team: 4,
      from: 'Ohio'
    },
    {
      id: 18,
      first_name: 'Arnold',
      last_name: 'Armstrong',
      sex: 'male',
      blood_type: 'AB',
      serve_years: 5,
      role: 'designer',
      team: 4,
      from: 'Nevada'
    },
    {
      id: 19,
      first_name: 'Eleanor',
      last_name: 'Smith',
      sex: 'female',
      blood_type: 'B',
      serve_years: 2,
      role: 'planner',
      team: 2,
      from: 'Texas'
    },
    {
      id: 20,
      first_name: 'Nina',
      last_name: 'Dawson',
      sex: 'female',
      blood_type: 'O',
      serve_years: 1,
      role: 'developer',
      team: 5,
      from: 'Virginia'
    },
    {
      id: 21,
      first_name: 'Jamie',
      last_name: 'Reynolds',
      sex: 'male',
      blood_type: 'AB',
      serve_years: 1,
      role: 'designer',
      team: 3,
      from: 'Arizona'
    },
    {
      id: 22,
      first_name: 'Myrtle',
      last_name: 'Hamilton',
      sex: 'female',
      blood_type: 'O',
      serve_years: 1,
      role: 'developer',
      team: 4,
      from: 'Maine'
    },
    {
      id: 23,
      first_name: 'Zelda',
      last_name: 'Hall',
      sex: 'female',
      blood_type: 'A',
      serve_years: 3,
      role: 'developer',
      team: 2,
      from: 'Tennessee'
    },
    {
      id: 24,
      first_name: 'Edgar',
      last_name: 'Willson',
      sex: 'male',
      blood_type: 'AB',
      serve_years: 2,
      role: 'designer',
      team: 4,
      from: 'Texas'
    },
    {
      id: 25,
      first_name: 'Brian',
      last_name: 'Hunt',
      sex: 'male',
      blood_type: 'B',
      serve_years: 2,
      role: 'planner',
      team: 1,
      from: 'Indiana'
    },
    {
      id: 26,
      first_name: 'Naomi',
      last_name: 'Taylor',
      sex: 'female',
      blood_type: 'O',
      serve_years: 5,
      role: 'developer',
      team: 4,
      from: 'Oklahoma'
    },
    {
      id: 27,
      first_name: 'Brooke',
      last_name: 'Evans',
      sex: 'male',
      blood_type: 'O',
      serve_years: 1,
      role: 'planner',
      team: 2,
      from: 'California'
    },
    {
      id: 28,
      first_name: 'Katherine',
      last_name: 'Bennett',
      sex: 'female',
      blood_type: 'A',
      serve_years: 3,
      role: 'designer',
      team: 5,
      from: 'Utah'
    },
    {
      id: 29,
      first_name: 'Violet',
      last_name: 'Pearson',
      sex: 'female',
      blood_type: 'AB',
      serve_years: 5,
      role: 'designer',
      team: 1,
      from: 'Pennsylvania'
    },
    {
      id: 30,
      first_name: 'Margaret',
      last_name: 'Davies',
      sex: 'female',
      blood_type: 'AB',
      serve_years: 2,
      role: 'developer',
      team: 5,
      from: 'California'
    },
    {
      id: 31,
      first_name: 'Raymond',
      last_name: 'Cook',
      sex: 'male',
      blood_type: 'O',
      serve_years: 1,
      role: 'planner',
      team: 3,
      from: 'Kansas'
    },
    {
      id: 32,
      first_name: 'Garth',
      last_name: 'Roberts',
      sex: 'male',
      blood_type: 'A',
      serve_years: 4,
      role: 'designer',
      team: 5,
      from: 'Minnesota'
    },
    {
      id: 33,
      first_name: 'Victoria',
      last_name: 'Brown',
      sex: 'female',
      blood_type: 'B',
      serve_years: 4,
      role: 'planner',
      team: 2,
      from: 'Georgia'
    },
    {
      id: 34,
      first_name: 'Russel',
      last_name: 'Jones',
      sex: 'male',
      blood_type: 'O',
      serve_years: 2,
      role: 'designer',
      team: 3,
      from: 'Nebraska'
    },
    {
      id: 35,
      first_name: 'Sally',
      last_name: 'Fox',
      sex: 'female',
      blood_type: 'AB',
      serve_years: 5,
      role: 'planner',
      team: 1,
      from: 'Messachusetts'
    },
    {
      id: 36,
      first_name: 'Chloe',
      last_name: 'Bailey',
      sex: 'female',
      blood_type: 'A',
      serve_years: 5,
      role: 'developer',
      team: 4,
      from: 'New York'
    },
    {
      id: 37,
      first_name: 'Will',
      last_name: 'McDonald',
      sex: 'male',
      blood_type: 'AB',
      serve_years: 1,
      role: 'developer',
      team: 5,
      from: 'Wyoming'
    },
    {
      id: 38,
      first_name: 'Grant',
      last_name: 'Cole',
      sex: 'male',
      blood_type: 'AB',
      serve_years: 4,
      role: 'designer',
      team: 3,
      from: 'Pennsylvania'
    },
    {
      id: 39,
      first_name: 'Audrey',
      last_name: 'Fisher',
      sex: 'female',
      blood_type: 'O',
      serve_years: 2,
      role: 'designer',
      team: 5,
      from: 'Ohio'
    },
    {
      id: 40,
      first_name: 'Amber',
      last_name: 'Russel',
      sex: 'female',
      blood_type: 'AB',
      serve_years: 5,
      role: 'developer',
      team: 3,
      from: 'Kansas'
    },
    {
      id: 41,
      first_name: 'Peter',
      last_name: 'Payne',
      sex: 'male',
      blood_type: 'O',
      serve_years: 3,
      role: 'planner',
      team: 4,
      from: 'California'
    },
    {
      id: 42,
      first_name: 'Russ',
      last_name: 'Lawrence',
      sex: 'male',
      blood_type: 'O',
      serve_years: 5,
      role: 'designer',
      team: 1,
      from: 'New York'
    },
    {
      id: 43,
      first_name: 'Margot',
      last_name: 'Watson',
      sex: 'female',
      blood_type: 'AB',
      serve_years: 2,
      role: 'planner',
      team: 5,
      from: 'Virginia'
    },
    {
      id: 44,
      first_name: 'Nancy',
      last_name: 'Brooks',
      sex: 'female',
      blood_type: 'A',
      serve_years: 1,
      role: 'developer',
      team: 2,
      from: 'California'
    },
    {
      id: 45,
      first_name: 'Oliver',
      last_name: 'Simpson',
      sex: 'male',
      blood_type: 'O',
      serve_years: 3,
      role: 'designer',
      team: 2,
      from: 'Arizona'
    },
    {
      id: 46,
      first_name: 'Pansy',
      last_name: 'Moore',
      sex: 'female',
      blood_type: 'B',
      serve_years: 2,
      role: 'planner',
      team: 3,
      from: 'Minnesota'
    },
    {
      id: 47,
      first_name: 'Leroy',
      last_name: 'Elliott',
      sex: 'male',
      blood_type: 'AB',
      serve_years: 2,
      role: 'developer',
      team: 1,
      from: 'Indiana'
    },
    {
      id: 48,
      first_name: 'Barbara',
      last_name: 'Murphy',
      sex: 'female',
      blood_type: 'O',
      serve_years: 1,
      role: 'developer',
      team: 5,
      from: 'Texas'
    },
    {
      id: 49,
      first_name: 'Simon',
      last_name: 'Henderson',
      sex: 'male',
      blood_type: 'A',
      serve_years: 4,
      role: 'designer',
      team: 2,
      from: 'Pennsylvania'
    },
    {
      id: 50,
      first_name: 'Ned',
      last_name: 'Butler',
      sex: 'male',
      blood_type: 'O',
      serve_years: 2,
      role: 'planner',
      team: 1,
      from: 'Messachusetts'
    }
  ],
  roles: [
    {
      id: 'developer',
      job: 'programming',
      requirement: 'computer science degree'
    },
    {
      id: 'designer',
      job: 'web design',
      requirement: 'graphic design certificate'
    },
    {
      id: 'planner',
      job: 'service planning',
      requirement: 'portfolio'
    }
  ],
  softwares: [
    {
      id: 'Eclipse',
      used_by: 'developer',
      developed_by: 'Eclipse Foundation',
      description: 'integrated development environment'
    },
    {
      id: 'Excel',
      used_by: 'planner',
      developed_by: 'Microsoft',
      description: 'spreadsheet'
    },
    {
      id: 'Illustrator',
      used_by: 'designer',
      developed_by: 'Adobe',
      description: 'vector graphics editor'
    },
    {
      id: 'Photoshop',
      used_by: 'designer',
      developed_by: 'Adobe',
      description: 'raster graphics editor'
    },
    {
      id: 'PowerPoint',
      used_by: 'planner',
      developed_by: 'Microsoft',
      description: 'presentation program'
    },
    {
      id: 'Sketch',
      used_by: 'designer',
      developed_by: 'Sketch B.V.',
      description: 'vector graphics editor'
    },
    {
      id: 'VS Code',
      used_by: 'developer',
      developed_by: 'Microsoft',
      description: 'source code editor'
    },
    {
      id: 'Word',
      used_by: 'planner',
      developed_by: 'Microsoft',
      description: 'word processor'
    },
    {
      id: 'Xcode',
      used_by: 'developer',
      developed_by: 'Apple',
      description: 'integrated development environment'
    }
  ],
  equipments: [
    {
      id: 'machanical keyboard',
      used_by: 'developer',
      count: 24,
      new_or_used: 'used'
    },
    {
      id: 'pen tablet',
      used_by: 'designer',
      count: 15,
      new_or_used: 'used'
    },
    {
      id: 'notebook',
      used_by: 'planner',
      count: 37,
      new_or_used: 'new'
    },
    {
      id: 'ergonomic mouse',
      used_by: 'designer',
      count: 31,
      new_or_used: 'used'
    },
    {
      id: 'dual monitor',
      used_by: 'developer',
      count: 20,
      new_or_used: 'used'
    },
    {
      id: 'whiteboard',
      used_by: 'planner',
      count: 12,
      new_or_used: 'used'
    },
    {
      id: 'sketchboard',
      used_by: 'designer',
      count: 48,
      new_or_used: 'new'
    }
  ],
  supplies: [
    { id: 'ergonomic mouse', team: 1 },
    { id: 'mug', team: 1 },
    { id: 'webcam', team: 2 },
    { id: 'hoodie', team: 2 },
    { id: 'chair', team: 3 },
    { id: 'usb hub', team: 3 },
    { id: 'headphone', team: 4 },
    { id: 'stempler', team: 4 },
    { id: 'calculator', team: 5 },
    { id: 't shirt', team: 5 }
  ]
}
[nodemon] clean exit - waiting for changes before restart


위와 같이 dbtester.js를 nodemon으로 기동하면 모든 항목들이 json 형태로 출력되는 걸 확인할 수 있어요.

dbtester.js


다시 코드를 위와 같이 수정해서 데이터베이스 안에 equipments 데이터만 반환 받도록 설정해 주었어요.

그럼 이제 equipments 데이터를 반환받을 수 있도록 작업해 볼게요.

최초 자료형을 어떻게 짜는지에 대해 알아볼게요.

[nodemon] restarting due to changes...
[nodemon] starting `node dbtester.js`
[
  {
    id: 'machanical keyboard',
    used_by: 'developer',
    count: 24,
    new_or_used: 'used'
  },
  {
    id: 'pen tablet',
    used_by: 'designer',
    count: 15,
    new_or_used: 'used'
  },
  { id: 'notebook', used_by: 'planner', count: 37, new_or_used: 'new' },
  {
    id: 'ergonomic mouse',
    used_by: 'designer',
    count: 31,
    new_or_used: 'used'
  },
  {
    id: 'dual monitor',
    used_by: 'developer',
    count: 20,
    new_or_used: 'used'
  },
  {
    id: 'whiteboard',
    used_by: 'planner',
    count: 12,
    new_or_used: 'used'
  },
  {
    id: 'sketchboard',
    used_by: 'designer',
    count: 48,
    new_or_used: 'new'
  }
]
[nodemon] clean exit - waiting for changes before restart


콘솔에 출력된 equipments 데이터를 살펴보면 위와 같은 데이터 형식을 가진걸 알 수 있어요.
즉 Count는 정수형으로 되어 있고, 나머지는 문자열로 이뤄진 것을 확인할 수 있어요.

index.js


index.js의 18 ~ 23번째 줄에 Equipment Type을 새로 작성해 주었어요.
이렇게 해서 반환할 Equipment Type이 만들어진거에요.

이제 Root Query Type에도 이 내용을 작성해 주어야 해요.

index.js


index.js의 6번째 줄에 위와 같이 추가해 주었어요.

이제 위 내용에 실제 Action(액션)을 취해줄 Resolver를 만들어 주어야 해요.

index.js

 

const resolvers = {
    Query: {
        teams: () => database.teams,
        equipments: () => database.equipments
    }
}


위와 같이 resolvers 객체에 화살표 함수를 이용해서 equipments 관련 Query 요청이 들어오면 데이터베이스의 equipments를 찾아 반환해 줄 수 있도록 작성을 해 주었어요. 

구동중인 nodemon을 종료(Ctrl + C)하고, 다시 Npm을 구동 시켜 볼게요.

npm start

 

http://localhost:4000


Playground에서 위와 같이 Query를 입력해서 날려보면 응답을 정상적으로 받는 걸 확인할 수 있어요.

 

 

 

 

        📦 supplies를 반환하는 Query를 만들어 보아요 😀

index.js


위에서 이야기한 내용대로 Supply에 대한 코드를 작성해 보았어요.

http://localhost:4000

 

{
  "data": {
    "equipments": [
      {
        "id": "machanical keyboard",
        "used_by": "developer",
        "count": 24,
        "new_or_used": "used"
      },
      {
        "id": "pen tablet",
        "used_by": "designer",
        "count": 15,
        "new_or_used": "used"
      },
      {
        "id": "notebook",
        "used_by": "planner",
        "count": 37,
        "new_or_used": "new"
      },
      {
        "id": "ergonomic mouse",
        "used_by": "designer",
        "count": 31,
        "new_or_used": "used"
      },
      {
        "id": "dual monitor",
        "used_by": "developer",
        "count": 20,
        "new_or_used": "used"
      },
      {
        "id": "whiteboard",
        "used_by": "planner",
        "count": 12,
        "new_or_used": "used"
      },
      {
        "id": "sketchboard",
        "used_by": "designer",
        "count": 48,
        "new_or_used": "new"
      }
    ],
    "supplies": [
      {
        "id": "ergonomic mouse",
        "team": 1
      },
      {
        "id": "mug",
        "team": 1
      },
      {
        "id": "webcam",
        "team": 2
      },
      {
        "id": "hoodie",
        "team": 2
      },
      {
        "id": "chair",
        "team": 3
      },
      {
        "id": "usb hub",
        "team": 3
      },
      {
        "id": "headphone",
        "team": 4
      },
      {
        "id": "stempler",
        "team": 4
      },
      {
        "id": "calculator",
        "team": 5
      },
      {
        "id": "t shirt",
        "team": 5
      }
    ]
  }
}


위와 같이 Equipments와 새로 추가된 Supplies에 대해 요청을 보내니 정상적으로 출력되는 걸 확인할 수 있어요.

 

 

 

        📦 조건에 따른 특정 데이터만 가져오기

CRUD를 작업할 때, 모든 데이터만 조회하진 않을거에요.
어떤 조건에 의해서 해당 조건에 맞는 데이터만 가져와야 하는 경우도 있어요.

그런 부분은 어떻게 처리할 수 있을까요?

 

Playgroud


현 상태에서 Playgroud를 통해 위와 같이 Teams의 ID를 조회하면 위와 같이 응답을 받는걸 확인할 수 있어요.
여기서 특정 조건을 줘서 해당 조건에 맞는 ID만 추출해서 응답 받을 수 있도록 해볼게요.

최초 이를 수행하는 Resolver 부터 만들어 볼거에요.

index.js

 

        team: (parent, args, context, info) =>
            database.teams.filter((team) => {
                return team.id === args.id;
            }),


위와 같이 Resolver 함수를 만들어 주었는데, 이 코드는 team이라는 Query를 만들어 주고, 해당 함수에 매개 변수로 parent, args, context, info를 받게 해 주었어요. 매개 변수에 대한 내용은 다른 글에서 조금 더 알아보도록 할게요.

일단 이 곳에서 우리가 주목해야 하는 매개 변수는 args(Arguments)에요.

해당 함수가 호출되면 database에서 teams를 모두 반환할 건데, Filter(화살표 함수로 받아온 Teams 데이터들을 각 팀이 어떤 기준으로 분류될지를 지정하는 함수를 재 지정)를 걸게 되고, 38번째 줄에 team의 ID가 매개변수 args에는 ID라는 항목이 있는데 이 ID와 같은 것만 반환하게 해 주었어요.

이 때, Filter이기 때문에 반환되는 데이터는 그 팀 하나가 반환되는 것이 아닌 해당 팀 하나가 들어있는 배열이 반환되게 돼요.

index.js


이 때, 위 39번째 줄과 같이 반환된 배열 중 0번째 배열만 반환하도록 처리해 볼게요.


여기까지 해 놓으면 콘솔은 위와 같이 난리가 났을 거에요.
왜냐하면 Resolver는 작성해 놓고, Type은 지정하지 않았기 때문이에요.
이번엔 기존 team에 대한 Root Query Type를 수정해 볼게요.

index.js


6번째 줄과 같이 추가를 해 주었는데 team이라는 Query는 매개 변수를 받는데 정수형 ID를 받게 되고, 반환 객체로 Team을 반환할 수 있도록 추가해 주었어요.


이렇게 되면 콘솔은 다시 평화를 찾게 될거에요.


Playgroud


Playgroud에서 위와 같이 ID가 1인 팀의 전체 정보를 갖고 오고 싶다고 Query를 날려주니 응답으로 ID가 1인 팀에 대한
정보를 받아오는 걸 확인할 수 있어요.



Playgroud


이번엔 ID가 4인 팀에 대해 조회를 요청했더니 위와 같이 4팀 정보를 반환 받은걸 확인할 수 있어요.

위에서 Query 요청 시 ID값이 바로 Resolver에서 매개 변수로 주었던 args에 들어가는 것이에요.
즉, 해당 매개 변수 이름에 따라 ID가 될 수도 있고, 다른 것을 지정해서 사용할 수도 있는 것이에요.

 

 

 

 

        📦 team과 supplies를 연결하여 반환하는 Query를 만들어 보아요 😀

이번에는 Team 목록을 반환할 때, 해당 Team에 해당하는 Supplies도 같이 응답으로 받을 수 있게 만들어볼게요.
참고로 Supplies에는 해당 팀의 번호를 항목으로 가지고 있어요.

index.js

반응형


기존 teams Query를 위와 같이 변경해 보았어요.

코드를 잠시 살펴볼게요.

Filter는 위에서 이야기 했듯 어떤 함수를 매개 변수로 받아 반환된 배열을 {} 안에 명시한 내용으로 걸러주는 함수에요.
위 코드에서는 데이터베이스의 supplies 배열 내용을 Filter하는데, 이 때 매개 변수로 supply를 넣어주게 되고, 해당 supply안에 team 내용과 team의 ID가 같은 것만 반환해달라고 한 거에요.

Map은 해당 배열을 특정 방식으로 변경해서 내보내는 역할을 하는 친구에요.

코드를 전체적으로 보면 35번째 줄에 데이터베이스의 teams 데이터를 받아온 다음 해당 배열들의 각 팀 데이터를 Supplies라는 항목에 넣고, 그것의 값으로는 데이터베이스에서 Supplies를 받아온 다음 해당 팀에 해당하는 것들만 걸러내서 다시 그 Supplies 데이터를 해당 팀에 넣은 뒤 그 팀 정보를 반환하는 함수에요.

index.js


기존 Root Query Type에 Team은 위와 같이 되어 있는데,

index.js


이렇게 19번째 줄과 같이 Supplies 정보(Supply 배열)도 함께 담을 수 있도록 선언해 주었어요.
 

Playgroud

 

{
  "data": {
    "teams": [
      {
        "id": 1,
        "manager": "Mandy Warren",
        "office": "101A",
        "extension_number": "#5709",
        "mascot": "Panda",
        "cleaning_duty": "Monday",
        "project": "Hyperion",
        "supplies": [
          {
            "id": "ergonomic mouse",
            "team": 1
          },
          {
            "id": "mug",
            "team": 1
          }
        ]
      },
      {
        "id": 2,
        "manager": "Stewart Grant",
        "office": "101B",
        "extension_number": "#4012",
        "mascot": "Tadpole",
        "cleaning_duty": "Tuesday",
        "project": "Zen",
        "supplies": [
          {
            "id": "webcam",
            "team": 2
          },
          {
            "id": "hoodie",
            "team": 2
          }
        ]
      },
      {
        "id": 3,
        "manager": "Smantha Wheatly",
        "office": "102A",
        "extension_number": "#3852",
        "mascot": "Falcon",
        "cleaning_duty": "Wednesday",
        "project": "Duranno",
        "supplies": [
          {
            "id": "chair",
            "team": 3
          },
          {
            "id": "usb hub",
            "team": 3
          }
        ]
      },
      {
        "id": 4,
        "manager": "Francis Buckley",
        "office": "103B",
        "extension_number": "#1039",
        "mascot": "Beaver",
        "cleaning_duty": "Thursday",
        "project": "Genghis",
        "supplies": [
          {
            "id": "headphone",
            "team": 4
          },
          {
            "id": "stempler",
            "team": 4
          }
        ]
      },
      {
        "id": 5,
        "manager": "Blake Smith",
        "office": "104A",
        "extension_number": "#7750",
        "mascot": "Wildcat",
        "cleaning_duty": "Friday",
        "project": "Acheron",
        "supplies": [
          {
            "id": "calculator",
            "team": 5
          },
          {
            "id": "t shirt",
            "team": 5
          }
        ]
      }
    ]
  }
}


Playgroud에서 위와 같이 teams 정보를 요청할 때, supplies에 대한 내용도 추가해서 요청하니 각 팀에 해당하는 Supplies 정보도 함께 가져오는 걸 확인할 수 있어요.

 

 

 

        📦 Mutation을 이용하여 데이터 삭제 API를 만들어 보아요 😀

위에서는 데이터베이스에 저장되어 있는 값들을 가져오는 Query에 대해 실습해 보았어요.
사실 Query를 이용해서도 Resolver를 어떻게 짰는가에 따라 데이터에 대해 삽입, 수정, 삭제를 할 수도 있어요.
또한, Mutation도 데이터를 가져올 수 있도록 구성할 수도 있어요.

하지만, REST API에서도 GET, POST, PUT/PATCH, DELETE에 따라 하는 행동들을 구분하듯이 QueryMutation
이와 동일한 어떤 약속이라고 생각하면 좋을 거 같아요.

이번에는 클라이언트에서 데이터베이스에 데이터를 저장, 수정, 삭제하는 Mutation에 대해 실습해 볼게요.

최초 Query와 마찬가지로 Mutaion도 Root Mutaion Type을 만들어 줘야 해요.

index.js


11 ~ 13번째 줄과 같이 Mutaion의 Root Mutaion Type을 만들어 주었어요.
그런 뒤 deleteEquipment()를 만들어 주는데, 이 때 매개 변수는 문자열 Type의 ID를 받고, 반환 값은 Equipment 객체라고 선언해 주었어요.

이번에는 해당 Equipment를 삭제하는 Resolver를 만들어 볼게요.

index.js


위와 같이 Mutaion Resolver를 선언하고, deleteEquipment()를 만들어 주었어요.
실제 서비스 코드에서는 데이터베이스 요청을 위한 ORM 관련 내용이라던지 SQL문이 들어갈텐데, 여기선 테스트이고, Mock 데이터를 처리하기 위함이기 때문에 위와 같이 짠 것이에요.

최초 58 ~ 61번째 줄까지 분석해 보면 deleteEquipment()는 삭제될 equipment를 반환하는 함수인데, 해당 ID로 Filtering된 삭제된 equipment에 대해 반환된 배열 중 첫번째 배열 값을 deleted 상수 변수에 값을 담고,   

63 ~ 65번째 줄까지는 데이터베이스안에 equipments를 조회하여 Filter를 걸어 삭제해 준 뒤 67번째 줄에 deleted 상수 변수를 반환해 주는 것이에요.


Playground

 

mutation {
  deleteEquipment(id: "notebook") {
    id
    used_by
    count
    new_or_used
  }
}


Playgroud에서 위와 같이 deleteEquipment()를 호출하는데, id가 notebook인 내용을 지우기 위해 위와 같이 매개 변수로 전달해주게 되면 삭제된 내용에 대한 객체가 반환되는 걸 확인할 수 있어요.

Playground


Query로 다시 equipments를 조회해 보면 ID가 notebook인 객체는 삭제되고 없는걸 확인할 수 있어요.


 

 

        📦 Mutation을 이용하여 데이터 삽입 API를 만들어 보아요 😀

이번에는 데이터 추가 즉, 삽입에 대해 알아볼게요.

index.js

 

    insertEquipment (
        id: String,
        used_by: String,
        count: Int,
        new_or_used: String
    ): Equipment


최초 12 ~ 17번째 줄과 같이 insertEquipment()를 만들어 주었는데, 데이터를 받아야 추가를 할 수 있으니 필요한 데이터들을 매개 변수로 받을 수 있도록 해 주었고, 반환값으로 생성된 Equipment 객체를 반환할 수 있도록 해 주었어요.

사실 실제 서비스 구성을 한다면 Equipment ID만 전달해 주는 것이 일반적이에요.

이제 Resolver를 만들어주어야 겠어요.

index.js

 

        insertEquipment: (parent, args, context, info) => {
            database.equipments.push(args);
            return args;
        },


위와 같이 insertEquipment()를 만들어 주었어요. 이 함수는 매개 변수로 parent, args, context, info를 받고, 데이터베이스의 equipments에 값을 넣는데, 클라이언트에서 요청으로 들어온 데이터를 입력하게 해 주었고, 이 입력된 데이터를 그대로 반환하는 함수에요.


Playgroud

 

mutation {
  insertEquipment (
    id: "macbook",
    used_by: "developer",
    count: 17,
    new_or_used: "new"
  ) {
    id
    used_by
    count
    new_or_used
  }
}


위와 같이 데이터를 추가하는 API 요청을 보내보았고, 결과로 추가된 데이터 객체를 반환 받는걸 확인할 수 있어요.


Playgroud


다시 한번 Query를 이용하여 equipments 데이터를 모두 긁어와 보면 위와 같이 방금 추가한 데이터가 들어간 것을 확인할 수 있어요.

 

 

 

        📦 Mutation을 이용하여 데이터 수정 API를 만들어 보아요 😀

마지막으로 수정 API에 대해서도 알아볼게요.

index.js


최초 위와 같이 Root Mutaion TypeupdateEquipment()를 만들어 주고, 매개 변수로 수정을 허용한 변수와 자료형 타입을 명시해 주었어요. 그런 뒤 반환 값으로는 수정된 객체를 반환할 수 있도록 해 주었어요.

그럼 이제 Resolver도 만들어 줘야겠죠?

index.js


위와 같이 ResolverupdateEquipment()를 만들어 주었어요.

 위 코드를 보면 최초 69번째 줄에서 데이터베이스 안에 equiments들을 모두 찾은 List에서 요청으로 보내진 ID에 해당하는 것을 찾고, args 객체로 주어진 데이터로 덮어 씌운 뒤 그 항목(배열) 중 0번째 배열을 반환하는 함수에요.

Playgroud


이번 테스트는 ID가 pen tablet인 것을 수정해 보려고 해요.

수정전에는 위와 같이 출력되는 걸 확인할 수 있어요.

Playgroud

 

mutation {
  updateEquipment (
    id: "pen tablet"
    new_or_used: "new",
    count: 30,
    used_by: "designer"
  ) {
    id
    new_or_used
    count
    used_by
  }
}


위와 같이 수정 요청을 보내기 위해 매개 변수로 수정할 데이터를 담아 요청을 보내면 반환 값을 받는걸 확인할 수 있고,


위와 같이 ID가 pen tablet인 객체 내용이 바뀐걸 확인할 수 있어요.



const database = require('./database');
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
  type Query {
    teams: [Team],
    team(id: Int): Team,
    equipments: [Equipment],
    supplies: [Supply]
  }
  
  type Mutation {
    insertEquipment (
        id: String,
        used_by: String,
        count: Int,
        new_or_used: String
    ): Equipment
    
    updateEquipment (
        id: String,
        used_by: String,
        count: Int,
        new_or_used: String
    ): Equipment
    
    deleteEquipment(id: String): Equipment 
  }
   
  type Team {
    id: Int,
    manager: String,
    office: String,
    extension_number: String,
    mascot: String,
    cleaning_duty: String,
    project: String,
    supplies: [Supply]
  }
  
  type Equipment {
    id: String
    used_by: String
    count: Int
    new_or_used: String
  }
  
  type Supply {
    id: String,
    team: Int
  }
`;
const resolvers = {
    Query: {
        teams: () => database.teams.map((team) => {
            team.supplies = database.supplies.filter((supply) => {
                return supply.team === team.id;
            });

            return team;
        }),

        team: (parent, args, context, info) =>
            database.teams.filter((team) => {
                return team.id === args.id;
            }) [0],

        equipments: () => database.equipments,
        supplies: () => database.supplies
    },

    Mutation: {
        insertEquipment: (parent, args, context, info) => {
            database.equipments.push(args);
            return args;
        },

        updateEquipment: (parent, args, context, info) => {
            return database.equipments.filter((equipment) => {
                return equipment.id === args.id;
            }).map((equipment) => {
                Object.assign(equipment, args);
                return equipment;
            }) [0];
        },

        deleteEquipment: (parent, args, context, info) => {
            const deleted = database.equipments.filter((equipment) => {
                return equipment.id === args.id
            })[0]

            database.equipments = database.equipments.filter((equipment) => {
                return equipment.id !== args.id
            })

            return deleted
        }
    }
}

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
    console.log(`🚀 주니의 Apollo 실습 Server 준비 완료! ${url}`);
})


지금까지 실습한 내용에 대한 index.js 내용이에요. 😀




 

 

GraphQL과 타입스크립트로 개발하는 웹 서비스:설계부터 개발·배포까지 따라 하며 완성하는 웹 풀

COUPANG

www.coupang.com

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

 

 

 

🧐 참고 자료

해당 내용은 아래 강의를 공부하면서 정리한 내용임을 알려 드립니다.

 

[무료] 얄팍한 GraphQL과 Apollo - 인프런 | 강의

⚡ 짧고 굵은 전체 90분 강좌! 사이트의 코드들을 복붙하며 빠르게 GraphQL을 배우고 아폴로 사용법을 익히세요., ⏱ 여러분의 시간은 소중합니다. [사진] 🎢  GraphQL이 뭔가요? 서비스를 구성하는

www.inflearn.com

 

 

 

 

카카오페이 | 마음 놓고 금융하다

여기를 눌러 링크를 확인하세요.

qr.kakaopay.com

 

 

 

728x90
반응형