올초에 GraphQL + yarn berry + typeScript + mySql로 프로젝트를 하려고 했다가 아무래도 프로젝트 시작이 어려워질것 같아서 익숙한 restAPI를 노선을 돌렸던 적이있다.
취업 준비를 하다가 우연히 GraphQL을 접하게 되어서 다시 GraphQL에 대해 차근차근 정리를 해보려고 한다.
graphQL은 SQL과 이름이 비슷하지만 쿼리언어이걸 제외하고는 아주 다르다.
graphQL은 서버와 클라이언트 사이에서 사용되는 것으로 restAPI와 비교하는 것이 더 정확하다.
rest는 원하는 데이터가 있을때마다 특정 url로 요청을 보내고, 서버는 그에 맞는 결과를 클라이언트에게 보내준다.
즉, 원하는 데이터가 바뀌면 그에 맞는 url을 요청해야한다.
graphQL은 요청자체에서 원하는 데이터를 명시해서 요청할 수 있다.
/grpahql이라는 post요청 하나로 데이터를 다채롭게 보낼수 있기때문에 여기에서 graphQL의 엔드포인트가 하나라는 장점이 탄생한 것이다.
그럼 graphQL에서 어떻게 쿼리를 보내는지 보자.
query는 읽기 전용으로, 데이터를 조회할 때 사용한다.
query {
// field
사용자(id: "123") {
이름
이메일
포스트 {
제목
내용
}
}
}
위의 쿼리는 "id"가 "123"인 사용자의 이름, 이메일 및 해당 사용자의 포스트의 제목과 내용을 가져오는 예시이다.
사용자라는 스키마와, 이름, 이메일등이 들어가는 부분을 field라고 한다.
예시와 같이 인자를 넣어서 요청함으로서 sql에 where절과 같은 역할을 할 수 있다.
Fragment는 쿼리의 재사용성과 가독성을 향상시키기 위해 사용된다.
query {
사용자(id: "123") {
이름
이메일
...포스트Fragment
}
}
fragment 포스트Fragment on 사용자 {
포스트 {
제목
내용
}
}
Fragment를 사용하면 여러 쿼리에서 동일한 필드 집합을 반복해서 작성하는 대신, 필요한 위치에서 Fragment를 참조하여 필드를 재사용할 수 있다.
위의 예시에서 포스트Fragment는 사용자 객체에 속하는 포스트 필드를 포함하는 Fragment로 사용자 객체의 쿼리에서 포스트 필드를 재사용할 수 있게된다.
그럼 서버에서는 어떻게 받아서 사용하는지 알아보자
스키마 파일에서는 graphQL에서 사용 가능한 타입, 필드 및 관계를 정의한다.
스키마 파일은 주로 SDL(Schema Definition Language) 형식으로 작성되며, 일반적으로 .graphql 확장자를 갖는다.
type 사용자 {
id: ID!
이름: String!
이메일: String!
포스트: [포스트!]!
}
type 포스트 {
id: ID!
제목: String!
내용: String!
작성자: 사용자!
}
type Query {
사용자(id: ID!): 사용자
포스트(id: ID!): 포스트
모든사용자: [사용자!]!
모든포스트: [포스트!]!
}
type Mutation {
생성사용자(이름: String!, 이메일: String!): 사용자!
생성포스트(제목: String!, 내용: String!, 작성자ID: ID!): 포스트!
수정사용자(id: ID!, 이름: String, 이메일: String): 사용자!
수정포스트(id: ID!, 제목: String, 내용: String): 포스트!
삭제사용자(id: ID!): ID!
삭제포스트(id: ID!): ID!
}
schema {
query: Query
mutation: Mutation
}
type은 데이터베이스에서 테이블 역할을 한다고 생각하면 쉽다.
type Query는 데이터를 가져오는데 사용되는 필드들을 정의하는 곳이고, type Mutation은 데이터를 생성, 수정, 삭제등을 할 때 필요한 필드를 정의하고 있다.
이렇게 스키마를 작성함으로서 타입, 필드 관계를 명시할 수 있다.
resolver는 graphQL 스키마에서 정의된 필드에 대한 실제 데이터를 반환하는 함수이다.
스키마에서 정의된 쿼리와 뮤테이션을 resolver 함수 내에서 작업을 수행한다.
리졸버 함수는 다음과 같은 구조를 가진다.
fieldName: (parent, args, context, info) => data;
parent: 부모 객체로, 필드가 중첩된 경우에 상위 객체를 참조한다.
args: 쿼리에서 전달된 인자. 인자에 따라 데이터를 필터링하거나 정렬한다.
context: 리졸버 함수 사이에서 공유되는 컨텍스트 객체. 주로 인증, 권한 부여, 데이터베이스 연결 등과 같은 작업을 수행하기 위해 사용된다.
info: 현재 쿼리의 실행에 대한 정보를 제공하는 객체. 스키마, 선택 집합, 실행 변수 등에 대한 정보
data: 필드의 결과 값 데이터베이스에서 쿼리한 결과, 외부 API의 응답 등..
Query 타입에 있는 사용자 필드는 다음과 같이 resolver 함수를 만들 수 있다.
const resolvers = {
Query: {
사용자: (parent, args, context, info) => {
// 필드를 해결하는 로직등이 들어간다.
// 데이터를 조회하거나 계산..
return 데이터;
}
}
};
정리
- GraphQL은 스키마를 통해 사용자가 원한느 명령어(쿼리, 뮤테이션, 리졸버,,) 등으로 응답을 효과적으로 처리해주는 api
- 필요한 정보만 선택해서 가져올 수 있으므로 resp api의 over-fetching 문제가 해결 된다.
- resp api에서 여러개의 데이터를 가져오려면 여러 요청을 해야하는 under-fetchin문제가 해결 된다.