Home GraphQL-기초
Post
Cancel

GraphQL-기초

공식 문서


1. GraphQL이란

GraphQL은 SQL과 마찬가지로 쿼리 언어 중 하나이다.

  • SQL: 데이터 베이스 시스템에 저장된 데이터를 효율적으로 가져오는 것이 목적
  • GraphQL:웹 클라이언트가 데이터를 서버로부터 효율적으로 가져오는 것이 목적

즉, 클라이언트와 서버간 통신을 위해 주로 사용된다.


1) 장점

  • 필요한 정보만 선택하여 받아올 수 있다.
    • RESTAPI의 Overfetching 문제 해결
    • 데이터 전송량 감소
  • 여러 계층의 정보들을 한 번에 받아올 수 있다.
    • RESTAPI의 Underfetching 문제 해결
    • 요청 횟수 감소
  • 하나의 Endpoint에서 모든 요청을 처리한다.
    • 하나의 URI에서 POST로 모든 요청이 가능하다.


2) Apollo

GraphQL은 RESPAPI와 마찬가지로 소프트웨어 통신을 원할 하게 위한 명세일 뿐이다.

그래서 GraphQL을 구현할 솔루션이 필요하다. 그 중 하나의 솔루션이 Apollo이다.

  • 백엔드에서 정보를 제공 및 처리

  • 프론트엔드에서 요청을 전송


2. Apollo 서버 구축하기

공식 문서

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const database = require('./database')
const { ApolloServer, gql } = require('apollo-server')

// typeDef: GraphQL 명세에서 사용될 데이터의 타입 (schema), 요청의 타입을 지정한다.
// 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 = {
  // teams를 요청하면, database의 team을 반환하는 함수를 실행한다. 
  Query: {
    teams: () => database.teams
  }
}

// ApolloServer 클래스는 typeDefs와 resiolvers를 인자로 받아서 서버를 생성한다.
const server = new ApolloServer({ typeDefs, resolvers })
server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`)
})


typeDef에 대해 좀 더 자세히 살펴보면 Query의 루트 타입이 지정되어 있음을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Query로 teams을 요청하면 Team의 배열을 확인할 수 있다 (요청에 대한 타입)
type Query {
    teams: [Team] 
}

// Team의 타입은 다음과 같다. (데이터에 대한 타입)
type Team {
    id: Int
    manager: String
    office: String
    extension_number: String
    mascot: String
    cleaning_duty: String
    project: String
}


1) Schemas and Types

공식 문서

GraphQL에서 데이터의 타입을 지정할 때, 사용되는 타입들은 정해져있다.

우선, GraphQL 스키마의 가장 기본적인 구성 요소는 객체 유형이다. 그 외에도 기본적인 타입들이 다음과 같이 존재한다.

타입설명
ID기본적으로는 String이나, 고유 식별자 역할임을 나타냄
StringUTF-8 문자열
Int부호가 있는 32비트 정수
Float부호가 있는 부동소수점 값
Boolean참/거짓
!null을 반환할 수 없다.
[Type]배열


이외에도 enum, union, interface, input 타입이 존재한다. (자세한 내용은 공식문서 참고)


2) Query (Read)

일반적으로 조건에 맞는 데이터를 찾으려는 경우가 많다.

그런 경우 인자를 사용하여 조건을 제한할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// typeDefs: id를 인자로 받아 해당 값을 가지고 있는 team을 반환한다. 
const typeDefs = gql`
  type Query {
  team(id: Int): Team
  }
`

// resolver 
const resolvers = {
  Query: {
    team: (parent, args, context, info) => database.teams
      .filter((team) => {
          return team.id === args.id
      })[0]
  }
}


여러 계층의 정보를 요청할 때는 아래와 같이 해결 할 수 있다. (UnderFetching 문제 해결 )

예를 들면, Team과 Supply가 1:N 관계일 때 Team의 타입에 supplies라는 필드를 만들어 Supply의 배열 타입을 추가한다.

그 후, Team의 데이터를 요청 할때 해당 Team에 맞는 Supply를 supplies 필드에 추가하는 로직을 넣어 Supply 데이터를 함께 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 해당 team이 소유하고 있는 supplies를 같이 요청한다. 
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
    supplies: [Supply]
  }
  
  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
      })
  }
}


3) Mutation (Create, Update, Delete)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 해당 팀을 삭제하고 삭제한 팀을 반환한다. 
const typeDefs = gql`
  type Mutation {
    deleteTeam(id:Int): Team
  }
`

const resolvers = {
   Mutatation: {
     deleteTeam: (parent, args, context, info) => {
         const deleted = database.teams
         .filter((team) => {
             return team.id === args.id
         })[0]
         database.team = database.teams
           .filter((team) => {
           return team.id !== args.id
            })
          return deleted
     }
   }
}


5. Request

공식 문서

클라이언트에서는 데이터를 요청하는 방식을 알아보면, 이는 기본적으로 특정한 필드를 요청하는 방식으로 되어 있다.

1
2
3
4
5
6
7
8
9
// teams라는 Action을 통해 Team 데이터를 요청한다. 
// Team 데이터의 id, manager, office 필드만 요청하고 있다. 
query requestTeam {
    teams {
        id
        manager
        office
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 반환 되는 배열을 별칭 myTeam으로 지정할 수도 있다. 
query requestTeam {
    myTeam: team {
        id
        manager
        office
    }
}

// 조건을 지정할 때는 다음과 같이 소괄호를 이용하여 인자를 전달한다. 
query requestTeam {
    team(id:1) {
        id
        manager
        office
    }
}


// 변수를 사용하여 인자에 전달할 수도 있다. (동적 쿼리)
// 선언된 변수는 scalars, enums, or input object 타입 중 하나여야 한다. 
query requestTeam($id: Id) {
    team(id: $id) {
        id
        manager
        office
    }
}


// 변수에 기본 값을 지정해 줄 수도 있다. 
// 변수를 사용하여 인자에 전달할 수도 있다. 
query requestTeam($id: Id = ABC) {
    team(id: $id) {
        id
        manager
        office
    }
}


// ...을 이용하여 타입에 제한을 줄 수도 있다. 
// 반환되는 데이터의 manager가 Developer 타입인지 Marketing 타입인지에 따라 반환되는 데이터가 다르다. 
query requestTeam {
    teams {
        manager
		... on Developer {
           office
        }
        
        ... on Marketing {
          id
        }
    }
}


타입이 복잡한 경우 Fragment를 사용하여 재사용 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const TeamInfo = gql`
  fragment teamInfo on Team {
    manager
    year
  }
`

const WorkInfo = gql`
  fragment workInfo on Team {
    office
    role
  }
`

const Get_TEAM = gql`
 query GetTeam {
   team {
     id
     ...workInfo
     ...teamInfo
   }
   
   ${WorkInfo}
   ${TeamInfo}
 }
`


또한, 지시어를 사용하여 좀 더 편리하게 데이터를 받을 수 있다.

1
2
3
4
5
6
7
8
9
const Get_TEAM = gql`
 query GetTeam($id: Int!, $checkManager:Boolean = true) {
   team(id: $id) {
    id
    manager @include(if: $checkManager)
    office
   }
 }
`
  • @include(if: Boolean): 인자가 true 인 경우에만 이 필드를 결과에 포함한다.
  • @skip(if: Boolean) 인자가 true 이면 이 필드를 건너뛴다.


This post is licensed under CC BY 4.0 by the author.