Halo World

[GraphQL] GraphQL 서버 깊이 파보기 (2) 본문

개발 지식/DEVELOPMENT

[GraphQL] GraphQL 서버 깊이 파보기 (2)

_Yeony 2022. 2. 13. 16:36

본 포스팅은 Inflearn 얄코님 강의를 수강하며 복습용으로 작성하였습니다.

https://www.inflearn.com/course/%EC%96%84%ED%8C%8D%ED%95%9C-graphql-apollo/dashboard

 

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

⚡ 짧고 굵은 전체 90분 강좌! 사이트의 코드들을 복붙하며 빠르게 GraphQL을 배우고 아폴로 사용법을 익히세요., - 강의 소개 | 인프런...

www.inflearn.com

 

유니언과 인터페이스


  • 이전 예제에서는 각각의 배열이 특정 스칼라 타입이나 특정 커스텀 데이터 타입을 반환했지만,
  • Union타입은 타입 여럿을 한 배열에 반환이 가능함

 

1. Equipment와 Supply를 함께 반환하기

  • given 데이터 타입 정의
//given.js 
//equipment와 supply를 함께 반환할 데이터 타입

const { gql } = require('apollo-server')
const dbWorks = require('../dbWorks.js')
const typeDefs = gql`
    union Given = Equipment | Supply   //Given 타입에 Equipment 또는 Supply를 받을 수 있음
`
const resolvers = {
    Query: {
    	//한 배열에 equipment와 supply를 둘다 넣어서 반환
        givens: (parent, args) => {
            return [
                ...dbWorks.getEquipments(args),
                ...dbWorks.getSupplies(args)
            ]
        }
    },
    Given: {
        __resolveType(given, context, info) {
        //해당 부분의 결과로 쿼리시 __typename 지정됨
            if (given.used_by) {
                return 'Equipment'
            }
            if (given.team) {
                return 'Supply'
            }
            return null
        }
    }
}
module.exports = {
    typeDefs: typeDefs,
    resolvers: resolvers
}
  • 정의한 resolver를 루트 쿼리에 추가
//_queries.js

const typeDefs = gql`
    type Query {
        // ...
        givens: [Given]
    }
`
  • index.js에 given 모듈 추가
//index.js

// ...
const givens = require('./typedefs-resolvers/givens')
// ...
const typeDefs = [
    // ...
    givens.typeDefs
]
// ...
const resolvers = [
    // ...
    givens.resolvers
]
// ...
  • playground에서 테스트 
  • givens 쿼리의 결과로 equipment와 supply 모두 반환
query {
  givens {
    __typename  //해당 데이터가 equipment인지 supply인지 나타내주기 위해 사용
    ... on Equipment {  //해당 데이터가 equipment인 경우 반환할 항목
      id
      used_by
      count
      new_or_used
    }
    ... on Supply {  //해당 데이터가 supply인 경우 반환할 항목
      id
      team
    }
  }
}

 

 

2. 인터페이스

  • 유사한 객체 타입을 만들기 위한 공통 필드 타입
  • 추상 타입 - 다른 타입에 implement 되기 위한 타입
  • Java의 인터페이스와 비슷한 역할
//공통으로 갖고있는 필드 : id, user_by

type Equipment {
    id: ID!
    used_by: Role!
    count: Int
    new_or_used: NewOrUsed!
}
...
type Software {
    id: ID!
    used_by: Role!
    developed_by: String!
    description: String
}
  • 공통 필드를 갖고있는 tools.js (인터페이스) 정의
//tools.js

const { gql } = require('apollo-server')
const typeDefs = gql`
    interface Tool {   //interface 정의
        id: ID!
        used_by: Role!
    }
`
const resolvers = {
    Tool: {
        __resolveType(tool, context, info) {
        //들어온 데이터가 어떤 타입인지 구분
            if (tool.developed_by) {
                return 'Software'
            }
            if (tool.new_or_used) {
                return 'Equipment'
            }
            return null
        }
    }
}
module.exports = {
    typeDefs: typeDefs,
    resolvers: resolvers
}
//Tool을 implement한 Equipment
type Equipment implements Tool {
    id: ID!
    used_by: Role!
    count: Int
    new_or_used: NewOrUsed!
}
//Tool을 implement한 Software
type Software implements Tool {
    id: ID!
    used_by: Role!
    developed_by: String!
    description: String
}
  • index.js에 tools import
//index.js

// ...
const tools = require('./typedefs-resolvers/tools')
// ...
const typeDefs = [
    // ...
    tools.typeDefs
]
// ...
const resolvers = [
    // ...
    tools.resolvers
]
// ...
  • People 쿼리에 적용
//peopls.js 정의

const { gql } = require('apollo-server')
const dbWorks = require('../dbWorks.js')
const typeDefs = gql`
    type People {
        id: ID!
        first_name: String!
        last_name: String!
        sex: Sex!
        blood_type: BloodType!
        serve_years: Int!
        role: Role!
        team: ID!
        from: String!
        tools: [Tool]  //tool 인터페이스 타입
        givens: [Given] // given 유니언 타입
    }
`
const resolvers = {
    Query: {
        people: (parent, args) => dbWorks.getPeople(args),
        person: (parent, args) => dbWorks.getPeople(args)[0]
    }
}
module.exports = {
    typeDefs: typeDefs,
    resolvers: resolvers
}
//_queries.js 루트 쿼리에 추가

const typeDefs = gql`
    type Query {
        people: [People],
        // ...
    }
`
//index.js에 모듈 추가

// ...
const people = require('./typedefs-resolvers/people')
// ...
const typeDefs = [
    // ...
    people.typeDefs
]
// ...
const resolvers = [
    // ...
    people.resolvers
]
// ...
  • playground에 쿼리 테스트
query {
  people {
    id
    first_name
    last_name
    givens {	//given 타입 쿼리 
        __typename
    	... on Equipment {
      	id
      	used_by
      	count
      	new_or_used
    	}
    	... on Supply {
      	id
      	team
    	}
  	}	
    tools {	//tools interface 타입에 대한 쿼리 
      __typename
      ... on Equipment {
        id
        used_by
        count
        new_or_used
      }
      ... on Software {
        id
        used_by
        description
        developed_by
      }
    }
  }
}​

 

+ 추가

Interface 타입과 Union 타입은 공통 필드를 갖고 있는지 없는지에 차이가 있음
Interface 타입을 구현한 객체는 Interface에 정의된 모든 필드를 갖고 있어야 함.

또한, 인터페이스 타입은 쿼리시 공통 부분으로 쿼리가 가능하다. 
예를 들어, Equipment와 Software이 공통으로 갖고있는 id, used_by를 사용해 특정 쿼리를 만들 수 있음
참고 : https://devport.tistory.com/21

 

 

인자와 인풋타입


People 데이터 조건들로 필터 넣어 받아오기

//_queries.js


    type Query {
        ...
        peopleFiltered(
            team: Int, 
            sex: Sex, 
            blood_type: BloodType, 
            from: String
        ): [People]
        ...
    }
  • __queries.js에서 정의한 대로 인자들을 받아와서 args로 인자들을 넘겨줄 수 있음
  • 받아서 처리하는 곳에서 sql 등 쿼리문을 사용하여 args를 기반으로한 where절 구현
//people.js

Query: {
    // ...
    peopleFiltered: (parent, args) => dbWorks.getPeople(args),
  }
  • playground에서 쿼리 호출
query {
  peopleFiltered (
    team: 1	//1 팀에 속하고
    blood_type: B	//b형인
    from: "Texas"	//Texas 출신만 쿼리
  ) {
    id
    first_name
    last_name
    sex
    blood_type
    serve_years
    role
    team
    from
  }
}

 

데이터를 페이지로 나누어 받아오기

  • paging 처리 방식
    //__queries.js
    
    type Query {
        ...
        peoplePaginated(
            page: Int!,
            per_page: Int!
        ): [People]
        ...
    }
//people.js

    Query: {
        // ...
        peoplePaginated: (parent, args) => dbWorks.getPeople(args), //내부에서 별도로 페이징 쿼리 진행
        // ...
    }
  • playground에서 쿼리 테스트
query {
//첫 페이지를 받아오고, 한 페이지당 7개의 결과를 받아와라
peoplePaginated(page: 1, per_page: 7) {	
    id
    first_name
    last_name
    sex
    blood_type
    serve_years
    role
    team
    from
  }
}

 

별칭으로 데이터 받아오기

  • 한 요청으로 데이터를 받아오는 경우, 데이터를 특정 기준으로 분리해서 받아오고 싶을 때 유용
query {
  badGuys: peopleFiltered(sex: male, blood_type: B) {
    first_name
    last_name
    sex
    blood_type
  }
  newYorkers: peopleFiltered(from: "New York") {
    first_name
    last_name
    from
  }
}

 

인풋 타입

  • mutation에 들어가는 인자들이 많아지는 경우, 이를 보다 깔끔하게 하기 위해 인자들을 묶어서 실어보내는 방식
//people.js

const typeDefs = gql`
    ....
    //실제 정보를 입력할 때 요구되는 필드들
    input PostPersonInput {
        first_name: String!
        last_name: String!
        sex: Sex!
        blood_type: BloodType!
        serve_years: Int!
        role: Role!
        team: ID!
        from: String!
    }
`
const resolvers = {
    // ...
    Mutation: {
        postPerson: (parent, args) => dbWorks.postPerson(args),
    }
}
//_mutation.js

type Mutation {
  //mutation의 postPerson 명령 실행시 들어가는 인자 정의 - 깔끔하게 묶어서 전달할 수 있음!
    postPerson(input: PostPersonInput): People!
    ...
}
  • playground 쿼리 테스트
mutation {
//인자로 input을 전달
//이전에 정의한 postPersonInput 형식으로 서버에 데이터 전송
  postPerson(input: {	
    first_name: "Hanna"
    last_name: "Kim"
    sex: female
    blood_type: O
    serve_years: 3
    role: developer
    team: 1
    from: "Pusan"
  }) {
    id
    first_name
    last_name
    sex
    blood_type
    role
    team
    from
  }
}

 

 

* Apollo 서버를 사용해 graphQL로 백앤드단을 구현하는 방법 간단 연습 끝

추가 정보는 아래 공식 사이트에서 튜토리얼로 배워볼 수 있음

https://www.apollographql.com/docs/apollo-server/#:~:text=Apollo+Server+is+an+open,use+data+from+any+source.