GraphQL with Apollo

김가영·2021년 4월 8일
0

Node.js

목록 보기
27/34
post-thumbnail

graphQL - 형식일 뿐

GraphQL을 구현할 솔루션 - BE에서 정보를 제공 및 처리하고, FE에서 요청 전송 - GraphQL.js, GraphQL Yoga...

→ Apollo : BE, FE 를 모두 제공한다. 간편하고 쉬운 설정

Apollo Server 구축하기

1. 서버 구축하기

npm i - apollo-server

  • index.js
const database = require('./database')
const { ApolloServer, gql } = require('apollo-server')

// graphQL 명세에서 사용될 데이터, 요청의 타입을 지정하는 것
// gql(template literal tag) 로 생성된다. 
const typeDefs = gql`
  type Query {
    teams: [Team]
  }
  type Team {
    id: Int
    manager: String
    office: String
    extension_number: String
    mascot: String
    cleaning_duty: String
    project: String
  }
`
// resolver : 서비스의 액션들을 함수로 지정(데이터를 반환, 입력, 수정, 삭제)
const resolvers = {
  Query: {
    teams: () => database.teams
  }
}
const server = new ApolloServer({ typeDefs, resolvers })

server.listen().then(({ url }) => {
console.log(`🚀  Server ready at ${url}`)
})
  • ApolloServer : typeDefsresolvers 를 받아 server를 return 한다.

  • typeDefs : graphQL 명세에서 사용될 데이터, 요청의 타입을 지정한다. gql로 생성된다

  const typeDefs = gql`
    type Query {
      teams: [Team]
    }
    type Team {
      id: Int
      manager: String
      office: String
      extension_number: String
      mascot: String
      cleaning_duty: String
      project: String
    }
  `

Query 명령문마다 사용될 쿼리들을 정의하고, 명령문마다 반환될 데이터 형태를 지정한다. 위에서는 teams는 Team 객체의 배열로 return 된다는 것을 명시

  • resolvers : 서비스의 액션(데이터를 반환, 입력, 수정, 삭제)들을 함수로 지정한다.
  const resolvers = {
    Query: {
      teams: () => database.teams
    }
  }

Query 라는, 데이터를 반환하는 함수를 새롭게 선언해준 것.

2. Query 구현하기

supplies와 equipment를 가져오는 기본 query

const database = require('./database')
const { ApolloServer, gql } = require('apollo-server')

// graphQL 명세에서 사용될 데이터, 요청의 타입을 지정하는 것
// gql(template literal tag) 로 생성된다. 
const typeDefs = gql`
  type Query {
    teams: [Team]
    equipments: [Equipment]
    supplies: [Supply]
  }
  type Team {
    id: Int
    manager: String
    office: String
    extension_number: String
    mascot: String
    cleaning_duty: String
    project: String
  }
  type Equipment {
    id: String
    used_by: String
    count: Int
    new_or_used: String
  }
  type Supply {
    id: String
    team: Int
  }
`
// resolver : 서비스의 액션들을 함수로 지정(데이터를 반환, 입력, 수정, 삭제)
const resolvers = {
  Query: {
    teams: () => database.teams,
    equipments : () => database.equipments,
    supplies : () => database.supplies,
  }
}
const server = new ApolloServer({ typeDefs, resolvers })

server.listen().then(({ url }) => {
console.log(`🚀  Server ready at ${url}`)
})

특정 id의 Team만 가져오는 방법

const database = require('./database')
const { ApolloServer, gql } = require('apollo-server')
 
const typeDefs = gql`
  type Query {
    ...
    team(id: Int): Team
  }
 	...
`
// resolver : 서비스의 액션들을 함수로 지정(데이터를 반환, 입력, 수정, 삭제)
const resolvers = {
  Query: {
    teams: () => database.teams,
    team: (parent, args, context, info) => database.teams.
      filter(item => {
        return item.id === args.id;
      })[0],
    ...
  }
}
const server = new ApolloServer({ typeDefs, resolvers })

server.listen().then(({ url }) => {
console.log(`🚀  Server ready at ${url}`)
})
  • query
  query {
    team(id:1) {
      id,
      manager,
      office
    }
  }

JOIN - 한 번에 여러개 가져오기

Team 에 supply 연결

const database = require('./database')
const { ApolloServer, gql } = require('apollo-server')

const typeDefs = gql`
  type Query {
    teams: [Team]
    equipments: [Equipment]
    supplies: [Supply]
    team(id: Int): Team
  }
  type Team {
    id: Int
    manager: String
    office: String
    extension_number: String
    mascot: String
    cleaning_duty: String
    project: String,
    supplies: [Supply]
  }
  ...
`
// resolver : 서비스의 액션들을 함수로 지정(데이터를 반환, 입력, 수정, 삭제)
const resolvers = {
  Query: {
    teams: () => database.teams
    .map((team) => {
      team.supplies = database.supplies
      .filter((supply) => {
        return supply.team === team.id
      })
      return team
    })
    ,
    ...
  }
}
const server = new ApolloServer({ typeDefs, resolvers })

server.listen().then(({ url }) => {
console.log(`🚀  Server ready at ${url}`)
})

TeamtypeDefssupplies를 추가하고, teams() 함수를 변경해줬다.

  • Query
  query {
    teams {
      id
      manager
  		supplies {
        id
        team
      }
    }
  }

3. Mutation

mutation? 데이터를 변경하는 것이다.

정보를 받아올 때는 query, 수정, 추가 시에는 mutation을 이용한다.

삭제

typeDefs에 type Mutation 추가

type Mutation {
    deleteEquipment(id: String): Equipment
  }

resolversdeleteEquipment 추가

Mutation: {
    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
    }

추가

  • typeDefs
  const typeDefs = gql`
  
    type Mutation {
      insertEquipment(
        id: String,
        used_by: String,
        count: Int,
        new_or_used: String
      ): Equipment
      ...
    }
    ...
    `

역시 resolver 에 추가한 후

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

params을 생략하면 null로 들어간다.

서버 구성요소 모듈화

typeDefsresolvers 는 단순한 Object 뿐만 아니라 Array 를 받을 수도 있다.

  • index.js
  const { ApolloServer } = require('apollo-server')
  
  const queries = require('./typedefs-resolvers/_queries')
  const mutations = require('./typedefs-resolvers/_mutations')
  const equipments = require('./typedefs-resolvers/equipments')
  
  const typeDefs = [
      queries,
      mutations,
      equipments.typeDefs,
  ]
  
  const resolvers = [
      equipments.resolvers
  ]
  
  const server =  new ApolloServer({typeDefs, resolvers})
  
  server.listen().then(({url}) => {
      console.log(`🚀  Server ready at ${url}`)
  })
  

root query들은 ./typedefs-resolvers/_queries 에, root mutation들은 ./typedefs-resolvers/_mutations에 위치

  • _queries.js
  const { gql } = require('apollo-server')
  
  const typeDefs = gql`
      type Query {
          equipments: [Equipment]
      }
  `
  
  module.exports = typeDefs
  • _mutations.js
const { gql } = require('apollo-server')

const typeDefs = gql`
    type Mutation {
      deleteEquipment(id: String): Equipment
    }
`

module.exports = typeDefs
  • typedefs-resolvers/equipments.js

    Equipment 에 대한 typeDefs와 resolver 함수들을 정의한다.

const { gql } = require('apollo-server')
const dbWorks = require('../dbWorks')

const typeDefs = gql`
    type Equipment {
        id: String
        used_by: String
        count: Int
        new_or_used: String
    }
`
const resolvers = {
    Query: {
        equipments: (parent, args) => dbWorks.getEquipments(args),
    },
    Mutation: {
        deleteEquipment: (parent, args) => dbWorks.deleteItem('equipments', args),
    }
}

module.exports = {
    typeDefs: typeDefs,
    resolvers: resolvers
}
  • 자세한 resolver함수는 dbWorks에 구현된다.

GraphQL 의 기본 타입들

스칼라 타입

    type EquipmentAdv {
        id: ID!
        used_by: String!
        count: Int!
        use_rate: Float
        is_new: Boolean!
    }
  • ID, String, Int, Float, Boolean

열거 타입(enum)

true, false 값만 반환하는 Boolean 처럼 정해진 값들만 반환하는 것

db에 엉뚱한 값이 들어가는 것을 막을 수 있다

  • _enums.js
const { gql } = require('apollo-server')
const typeDefs = gql`
    enum Role {
        developer
        designer
        planner
    }
    enum NewOrUsed {
        new
        used
    }
`
module.exports = typeDefs
  • index.js
const enums = require('./typedefs-resolvers/_enums')

const typeDefs = [
    enums,
    ...
]
...
  • 빈 배열은 not null!

유니언과 인터페이스

유니언

  • Equipment와 Supply를 함께 반환하기

  • givens.js

const { gql } = require('apollo-server')
const dbWorks = require('../dbWorks.js')

const typeDefs = gql`
    union Given = Equipment | Supply
`
const resolvers = {
    Query: {
        givens: (parent, args) => {
            return [
                ...dbWorks.getEquipments(args),
                ...dbWorks.getSupplies(args)
            ]
        }
    },
    Given: {
        __resolveType(given, context, info) {
            if (given.used_by) {
                return 'Equipment'
            }
            if (given.team) {
                return 'Supply'
            }
            return null
        }
    }
}
module.exports = {
    typeDefs: typeDefs,
    resolvers: resolvers
}
  • __resolveType : __typename을 지정해준다.
query {
  givens {
    __typename
    ... on Equipment {
      id
      used_by
      count
      new_or_used
    }
    ... on Supply {
      id
      team
    }
  }
}
  • __typename : Given 함수의 __resolveType 에서 리턴해준 것

  • ... on Equipment : __typename 이 'Equipment' 일때 반환할 field


Interface

  • tools.js
const { gql } = require('apollo-server')
const typeDefs = gql`
    interface Tool {
        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
}
  • equipments.js
type Equipment implements Tool {
    id: ID!
    used_by: Role!
    count: Int
    new_or_used: NewOrUsed!
}
  • softwares.js
type Equipment implements Tool {
    id: ID!
    used_by: Role!
    count: Int
    new_or_used: NewOrUsed!
}

인자와 인풋 타입

  • _queries.js
    type Query {
        ...
        peopleFiltered(
            team: Int, 
            sex: Sex, 
            blood_type: BloodType, 
            from: String
        ): [People]
        ...
    }
  • query
query {
  peopleFiltered (
    team: 1
    blood_type: B
		sex:female
  ) {
    id
    first_name
    last_name
    sex
    blood_type
    serve_years
    role
    team
    from
  }
}

enum 은 ""를 쓰지 않는다!

별칭으로 받아오기

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
  }
}

인풋 타입

  • 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 {
    postPerson(input: PostPersonInput): People!
    ...
}
  • Query
mutation {
  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
  }
}
profile
개발블로그

0개의 댓글