GraphQL이란?
API의 쿼리 언어이자 데이터에 대한 요청을 실행하는 런타임
REST와 달리 GraphQL은 여러 엔드포인트를 노출하는 대신 정확히 필요한 데이터를 쿼리할 수 있는 단일 엔드포인트를 노출함
GraphQL의 주요 개념
- 스키마 정의 언어 (Schema Definition Language, SDL)
- 스키마 (Schema)
- GraphQL 스키마는 API에서 유형과 그들 간의 관계를 정의함
- 클라이언트와 서버 간의 계약으로 작용함
- 유형: 스키마의 기본 단위
- 객체 타입 (Object Types): 특정 객체의 필드
- 스칼라 타입 (Scalar Types): 기본 데이터 타입(예:
String
, Int
, Float
, Boolean
, ID
). - 열거형 타입 (Enum Types): 미리 정의된 값 집합을 나열한 필드
- 인터페이스 (Interfaces): 여러 타입에서 공통적으로 사용되는 필드
- 유니온 (Unions): 여러 객체 타입 중 하나를 반환할 수 있는 타입
- 입력 타입 (Input Types): 뮤테이션에서 인자로 사용되는 객체 타입
- 쿼리 (Query)
- 클라이언트가 데이터를 요청하는 방법
- 필요한 데이터와 구조를 정확히 지정하여 클라이언트가 단일 요청에서 필요한 모든 데이터를 가져올 수 있음
- 예시 쿼리:
1
2
3
4
5
6
7
8
9
| {
user(id: "1") {
name
age
posts {
title
}
}
}
|
- 뮤테이션 (Mutation)
- 클라이언트가 서버의 데이터를 수정하는 방법
- 데이터를 생성, 업데이트 또는 삭제할 수 있음
- 예시 뮤테이션:
1
2
3
4
5
6
| mutation {
createUser(input: { name: "John", age: 28 }) {
id
name
}
}
|
- 리졸버 (Resolver)
- 스키마의 각 필드에 대한 데이터 검색 로직을 처리하는 함수
- 쿼리에 따라 필드의 값을 해결함
- 구독 (Subscription)
- 서버와의 지속적인 연결을 유지하여 서버가 실시간 업데이트를 클라이언트에게 보낼 수 있게 함
GraphQL의 장점
- 클라이언트 지정 쿼리
- 클라이언트는 필요한 데이터를 정확하게 요청할 수 있으므로 over-fetching과 under-fetching를 피할 수 있음
- Strongly Typed Schema
- 스키마와 유형을 통해 쿼리가 유형 시스템에 대해 유효성을 검사하므로 오류가 줄어들고 개발자 경험이 향상됨
- 단일 엔드포인트
- REST와 달리 GraphQL은 모든 작업에 대해 단일 엔드포인트를 사용하므로 API를 단순화할 수 있음
- 유연성과 효율성
- 클라이언트는 서버와 독립적으로 진화할 수 있으며 개발자는 기존 쿼리에 영향을 주지 않고 새로운 필드와 유형을 추가할 수 있음
- Introspection
- GraphQL API는 자체 문서화되므로 클라이언트는 유형 및 작업을 이해하기 위해 스키마 자체를 쿼리할 수 있음
예시 스키마 정의
** 쿼리 (Query)**
1
2
3
4
5
6
7
| {
user(id: "1") {
id
name
age
}
}
|
뮤테이션 (Mutation)
1
2
3
4
5
6
7
| mutation {
createUser(name: "Alice", age: 30) {
id
name
age
}
}
|
구독 (Subscription)
1
2
3
4
5
6
7
| subscription {
userCreated {
id
name
age
}
}
|
프래그먼트 (Fragments)
재사용 가능한 쿼리 조각
1
2
3
4
5
6
7
8
9
10
11
| fragment userFields on User {
id
name
age
}
{
user(id: "1") {
...userFields
}
}
|
변수 (Variables)
쿼리나 뮤테이션에서 동적 값을 전달하는 데 사용됨
1
2
3
4
5
6
7
| query getUser($id: ID!) {
user(id: $id) {
id
name
age
}
}
|
디렉티브 (Directives)
쿼리의 실행 방식을 제어하는 데 사용됨
- @include: 조건에 따라 필드 포함
1
2
3
4
5
6
7
| query getUser($withEmail: Boolean!) {
user(id: "1") {
id
name
email @include(if: $withEmail)
}
}
|
- @skip: 조건에 따라 필드 생략
1
2
3
4
5
6
7
| query getUser($withoutEmail: Boolean!) {
user(id: "1") {
id
name
email @skip(if: $withoutEmail)
}
}
|
에일리어스 (Aliases)
에일리어스를 사용하여 동일한 필드를 여러 번 요청할 때 별칭을 부여할 수 있음
1
2
3
4
5
6
7
8
9
10
| {
user1: user(id: "1") {
id
name
}
user2: user(id: "2") {
id
name
}
}
|
내장 타입 (Scalar Types)
Int
: 정수Float
: 부동 소수점 숫자String
: 문자열Boolean
: 참/거짓 값ID
: 고유 식별자
커스텀 타입 (Custom Types)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| type User {
id: ID!
name: String!
age: Int
email: String
posts: [Post]
}
type Post {
id: ID!
title: String!
content: String
author: User
}
|
뮤테이션 인자로 사용되는 객체 타입
1
2
3
4
5
6
7
8
| input CreateUserInput {
name: String!
age: Int
}
type Mutation {
createUser(input: CreateUserInput): User
}
|
인터페이스 (Interfaces)
여러 타입에 공통으로 사용되는 필드를 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| interface Character {
id: ID!
name: String!
}
type Human implements Character {
id: ID!
name: String!
starships: [Starship]
}
type Droid implements Character {
id: ID!
name: String!
primaryFunction: String
}
|
유니온 타입 (Union Types)
여러 타입 중 하나를 반환할 수 있음
1
2
3
4
5
| union SearchResult = Human | Droid
type Query {
search(text: String!): [SearchResult]
}
|
스키마 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| schema {
query: Query
mutation: Mutation
subscription: Subscription
}
type Query {
user(id: ID!): User
allUsers: [User]
}
type Mutation {
createUser(name: String!, age: Int): User
}
type Subscription {
userCreated: User
}
|
REST와 비교
- 유연성: REST 엔드포인트는 고정된 데이터 구조를 반환하지만 GraphQL 쿼리는 요청된 데이터만 반환함
- 효율성: GraphQL은 클라이언트가 관련 데이터를 단일 요청에서 가져오도록 하므로 네트워크 요청 수를 최소화함
- 개발 속도: GraphQL의 유형 시스템과 자체 문서화 특성은 개발 및 디버깅을 가속화됨