UI와 API의 연동에 들어가기 앞서, Axios와 GraphQL을 사용하여 데이터를 관리하는 방식에 대해 공부해 보고자 글을 작성하게 되었습니다.
GraphQL은 API를 위한 쿼리 언어이며 타입 시스템을 사용하여 쿼리를 실행하는 서버사이드 런타임입니다. GraphQL은 특정한 데이터베이스나 특정한 스토리지 엔진과 관계되어 있지 않으며 기존 코드와 데이터에 의해 대체됩니다.
클라이언트는 다음과 같이 GraphQL 서버로 Query를 전송하고,
{
me {
name
}
}
서버는 Query를 해석하여 클라이언트가 요청한 만큼의 데이터를 JSON 형태로 반환합니다.
{
"me": {
"name": "Gong You"
}
}
GraphQL은 클라이언트가 요청한 데이터만 반환하므로 over fetching을 줄여 효율적이며, 스키마를 사용하여 데이터 모델을 정의하기 때문에 클라이언트와 서버 간의 일관성 있는 데이터 통신을 보장합니다.
HTTP 요청 방식으로 GET, POST, PUT, PATCH, DELETE가 있듯이 GraphQL에는 Query, Mutation, Subscription이라는 요청 방식이 있습니다.
Query : read(GET)
Mutation : create, update, delete(POST, PUT, PATCH, DELETE)
Subscription : realtime connection via Websockets
Query는 데이터를 읽는 작업(GET)을 담당하며 Mutation은 데이터를 생성(POST), 업데이트(PUT, PATCH), 삭제(DELETE)하는 작업을 담당합니다. 그리고 SubscriptIon은 실시간으로 변경된 데이터를 가져오기 위한 요청 방식으로 웹소켓을 통한 실시간 양방향 통신을 구현할 때 사용됩니다.
Query와 Mutation 작업을 간단한 예시와 함께 살펴보겠습니다.
query {
users {
id
name
email
}
}
이 Query는 서버에게 users 필드에서 각 사용자의 id, name, email을 가져오라고 요청합니다.
mutation {
addUser(input: { name: "Gongyou", email: "Gongyou@example.com" }) {
id
name
email
}
}
이 Mutation은 서버에게 새로운 사용자를 추가하도록 요청합니다. addUser Mutation은 input 객체를 받아 사용자의 이름과 이메일을 전달하고, 서버는 추가된 사용자의 id, name, email을 반환합니다.
GraphQL의 Schema와 Type은 GraphQL 서버가 클라이언트 요청을 처리하기 위한 구조와 규칙을 정의하는데 사용됩니다. GraphQL에서는 객체 타입(Object Type)을 사용하여 데이터를 정의합니다.
학생과 관련된 타입을 정의해 보겠습니다.
type Student {
id: ID!
name: String!
email: String!
courses: [Course!]!
}
Student는 GraphQL의 객체 타입이고 id, name, email, courses라는 필드(Field)를 가지고 있습니다. ID, String, []는 필드의 타입을 나타내며 타입 뒤에 붙은 느낌표는 해당 필드가 필수로 즉, 항상 존재해야 함을 의미합니다.
type Student 정의를 통해 생성될 수 있는 예시 데이터를 살펴보겠습니다.
{
"id": "1",
"name": "Gong you",
"email": "Gongyou@example.com",
"courses": [
{
"id": "101",
"name": "Introduction to GraphQL",
"description": "Learn the basics of GraphQL"
},
{
"id": "102",
"name": "Advanced GraphQL",
"description": "Deep dive into GraphQL features"
}
]
}
학생과 관련된 작업을 정의하는 전체 schema를 작성해 보겠습니다.
// 클라이언트가 서버에 요청할 수 있는 작업 정의
schema {
query: Query
mutation: Mutation
}
// 학생과 관련된 타입 정의
type Student {
id: ID!
name: String!
email: String!
courses: [Course!]!
}
type Course {
id: ID!
name: String!
description: String
}
// 학생 데이터를 조회하는 Query
type Query {
student(id: ID!): Student
students: [Student!]!
}
// 새로운 학생을 추가하는 Mutation
type Mutation {
addStudent(name: String!, email: String!): Student!
}
이제 axios를 사용하여 GraphQL 서버로부터 학생 데이터를 조회하고 추가하는 코드를 작성해 보겠습니다.
먼저 학생 데이터를 조회하는 코드입니다.
const GRAPHQL_ENDPOINT = 'http://localhost:4000/';
const GET_STUDENTS_QUERY = `
query {
students {
id
name
email
courses
}
}
`;
const [students, setStudents] = useState([]);
useEffect(() => {
fetchStudents();
}, []);
const fetchStudents = async () => {
try {
const response = await axios.post(GRAPHQL_ENDPOINT, {
query: GET_STUDENTS_QUERY,
});
setStudents(response.data.data.students);
} catch (error) {
console.error(error);
}
};
코드를 살펴보면 axios를 사용하여 GraphQL 서버에 POST 요청을 보내고, 이때 필요로 하는 데이터를 query로 작성하여 요청합니다. 그리고 서버로부터 응답받은 데이터를 상태로 저장합니다.
다음은 학생 데이터를 추가하는 코드입니다.
const GRAPHQL_ENDPOINT = 'http://localhost:4000/';
const ADD_STUDENT_MUTATION = `
mutation ($name: String!, $email: String!, $courses: [String!]!) {
addStudent(name: $name, email: $email, courses: $courses) {
id
name
email
courses
}
}
`;
const [students, setStudents] = useState([]);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [courses, setCourses] = useState('');
const addStudent = async () => {
try {
const response = await axios.post(GRAPHQL_ENDPOINT, {
query: ADD_STUDENT_MUTATION,
variables: {
name,
email,
courses: courses.split(','),
},
});
setStudents([...students, response.data.data.addStudent]);
setName('');
setEmail('');
setCourses('');
} catch (error) {
console.error(error);
}
};
코드를 살펴보면 axios를 사용하여 GraphQL 서버에 POST 요청을 보내고, 이때 필요로 하는 데이터를 query로 작성하고 varialbes 객체를 포함하여 학생 정보를 전달합니다. 이때 query는 데이터 추가를 요청하는 mutation query이며, variables는 mutation query에 필요한 변수를 객체 형태로 작성한 것입니다. 서버로부터 응답받은 데이터를 상태로 저장하고 업데이트할 수 있습니다.
이쯤되면 스멀스멀 생기는 아하😮 포인트가 있을텐데요,,
바로,, 지금까지 살펴본 GraphQL을 이용한 코드들이 모두 POST 요청과 단일 End Point(코드 상에서는 GRAPHQL_ENDPOINT)만을 사용한다는 사실..!
우리가 그동안 자주 사용하고 접한 REST API의 경우 다음과 같이 여러 개의 End Point를 사용해왔습니다.
하지만 이를 GraphQL의 경우로 적용해보면 다음과 같이 1개의 End Point만으로도 서버와 통신할 수 있습니다.
또한 REST API를 사용해서 데이터를 요청할 때, 고정된 형식으로만 요청 가능하기 때문에 필요하지 않은 데이터까지 받아 over fetching이 발생하는 경우가 있습니다. 하지만 GraphQL은 필요한 데이터만 요청하여 받아올 수 있다는 점에서 REST API보다 효율적이라고 볼 수 있습니다.
GraphQL은 Query와 Schema를 사용하여 필요한 데이터만 서버에 요청할 수 있는 Query 언어이자 서버사이드 런타임입니다.
GraphQL은 over fetching을 줄이고 클라이언트와 서버의 일관성 있는 통신을 보장합니다.
더 나아가 GraphQL을 사용하여 클라이언트 측에서 데이터를 요청하고 관리할 수 있습니다.
GraphQL 소개
React에 GraphQL 한스푼 더하기: 1. graphQL 맛보기
[GraphQL] 2. GraphQL의 메서드 query, mutation, subscription 에 대해 알아보자
GraphQL에 대하여 (장점 & Schema & Type)