웹 개발에서 애플리케이션을 최적화하고 확장하는 것은 항상 문제였습니다. React.js는 프론트엔드 개발에서 도구로서 뛰어난 성공을 거두며 사용자 인터페이스를 만드는 견고한 방법을 제공했습니다. 그러나 애플리케이션이 커질수록 특히 여러 REST API 엔드포인트가 있는 경우 복잡해집니다. 과도한 데이터가 필요한 오버페칭과 같은 문제는 성능 병목 현상과 나쁜 사용자 경험의 원인이 될 수 있습니다.
이러한 도전에 대한 해결책 가운데 하나는 React 애플리케이션에서 GraphQL을 채택하는 것입니다. 백엔드에 여러 REST 엔드포인트가 있는 경우 내부적으로 REST API 엔드포인트를 호출하는 GraphQL 레이어를 도입하면 오버페칭을 방지하고 프론트엔드 애플리케이션을 최적화할 수 있습니다. 이 기사에서는 GraphQL을 사용하는 방법, 이 접근 방식의 장단점, 다양한 도전 과제 및 이를 해결하는 방법에 대해 알아볼 수 있습니다. 또한 GraphQL이 데이터 처리 방식을 개선하는 데 어떻게 도움을 줄 수 있는지에 대해 실용적인 예제를 자세히 살펴볼 것입니다.
REST API에서의 오버페칭
REST API에서 오버페칭은 API가 클라이언트에 제공하는 데이터 양이 클라이언트가 필요한 것보다 많을 때 발생합니다. 이는 REST API에서 자주 발생하는 문제로, 종종 고정된 객체나 응답 스키마를 반환합니다. 이 문제를 더 잘 이해하기 위해 예를 살펴보겠습니다.
사용자 프로필 페이지를 고려해보겠습니다. 사용자의 이름
과 이메일
만 표시하면 되는 경우를 고려해보겠습니다. 전형적인 REST API를 사용하여 사용자 데이터를 가져오면 다음과 같을 것입니다:
fetch('/api/users/1')
.then(response => response.json())
.then(user => {
// Use the user's name and profilePicture in the UI
});
API 응답에는 불필요한 데이터가 포함됩니다:
{
"id": 1,
"name": "John Doe",
"profilePicture": "/images/john.jpg",
"email": "[email protected]",
"address": "123 Denver St",
"phone": "111-555-1234",
"preferences": {
"newsletter": true,
"notifications": true
},
// ...more details
}
사용자가 이름과 이메일 필드만 필요로 하는 애플리케이션임에도 불구하고, API는 전체 사용자 객체를 반환합니다. 이러한 추가 데이터는 종종 페이로드 크기를 증가시키고, 더 많은 대역폭을 소모하며, 궁극적으로 자원이 제한된 장치나 느린 네트워크 연결에서 애플리케이션의 속도를 저하시킬 수 있습니다.
GraphQL을 솔루션으로
GraphQL은 클라이언트가 필요한 데이터를 정확히 요청할 수 있도록 하여 과다 요청 문제를 해결합니다. 애플리케이션에 GraphQL 서버를 통합함으로써, 기존 REST API와 통신하는 유연하고 효율적인 데이터 가져오기 계층을 만들 수 있습니다.
작동 원리
1. GraphQL 서버 설정
React 프론트엔드와 REST API 간의 중개 역할을 하는 GraphQL 서버를 도입합니다.
2. 스키마 정의
프론트엔드에서 필요한 데이터 유형과 쿼리를 지정하는 GraphQL 스키마를 정의합니다.
3. 리졸버 구현
REST API에서 데이터를 가져오고 필요한 필드만 반환하는 리졸버를 GraphQL 서버에 구현합니다.
4. 프론트엔드 통합
React 애플리케이션을 업데이트하여 직접 REST API 호출 대신 GraphQL 쿼리를 사용하도록 합니다.
이 접근 방식은 기존 백엔드 인프라를 대대적으로 변경하지 않고도 데이터 가져오기를 최적화할 수 있게 해줍니다.
React 애플리케이션에서 GraphQL 구현
GraphQL 서버를 설정하고 이를 React 애플리케이션에 통합하는 방법을 살펴보겠습니다.
종속성 설치
npm install apollo-server graphql axios
스키마 정의
schema.js
라는 파일을 만듭니다:
const { gql } = require('apollo-server');
const typeDefs = gql`
type User {
id: ID!
name: String
email: String // Ensure this matches exactly with the frontend query
}
type Query {
user(id: ID!): User
}
`;
module.exports = typeDefs;
이 스키마는 User
타입과 ID로 사용자를 가져오는 user
쿼리를 정의합니다.
해결자 구현
resolvers.js
라는 파일을 만듭니다:
const resolvers = {
Query: {
user: async (_, { id }) => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const user = await response.json();
return {
id: user.id,
name: user.name,
email: user.email, // Return email instead of profilePicture
};
} catch (error) {
throw new Error(`Failed to fetch user: ${error.message}`);
}
},
},
};
module.exports = resolvers;
user
쿼리의 해결자는 REST API에서 데이터를 가져오고 필요한 필드만 반환합니다.
우리는 가짜 REST API로 https://jsonplaceholder.typicode.com/를 사용할 것입니다.
서버 설정
server.js
파일을 만듭니다:
const { ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen({ port: 4000 }).then(({ url }) => {
console.log(`GraphQL Server ready at ${url}`);
});
서버를 시작합니다:
node server.js
당신의 GraphQL 서버는 http://localhost:4000/graphql에서 실행 중이며, 서버에 쿼리를 하면 이 페이지로 이동합니다.
React 애플리케이션과 통합
이제 React 애플리케이션을 GraphQL API를 사용하도록 변경하겠습니다.
Apollo 클라이언트 설치
npm install @apollo/client graphql
Apollo 클라이언트 구성
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000',
cache: new InMemoryCache(),
});
GraphQL 쿼리 작성
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
이제 위의 코드 조각들을 React 애플리케이션과 통합합니다. 아래는 사용자가 userId를 선택하고 정보를 표시할 수 있는 간단한 React 앱입니다:
import { useState } from 'react';
import { ApolloClient, InMemoryCache, ApolloProvider, gql, useQuery } from '@apollo/client';
import './App.css'; // Link to the updated CSS
const client = new ApolloClient({
uri: 'http://localhost:4000', // Ensure this is the correct URL for your GraphQL server
cache: new InMemoryCache(),
});
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
const User = ({ userId }) => {
const { loading, error, data } = useQuery(GET_USER, {
variables: { id: userId },
});
if (loading) return <p>Loading</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div className="user-container">
<h2>{data.user.name}</h2>
<p>Email: {data.user.email}</p>
</div>
);
};
const App = () => {
const [selectedUserId, setSelectedUserId] = useState("1");
return (
<ApolloProvider client={client}>
<div className="app-container">
<h1 className="title">GraphQL User Lookup</h1>
<div className="dropdown-container">
<label htmlFor="userSelect">Select User ID:</label>
<select
id="userSelect"
value={selectedUserId}
onChange={(e) => setSelectedUserId(e.target.value)}
>
{Array.from({ length: 10 }, (_, index) => (
<option key={index + 1} value={index + 1}>
{index + 1}
</option>
))}
</select>
</div>
<User userId={selectedUserId} />
</div>
</ApolloProvider>
);
};
export default App;
결과
당신은 다음과 같은 간단한 사용자 세부 정보를 볼 수 있습니다: [Github 링크].
여러 엔드포인트 사용하기
특정 사용자의 게시물을 검색하고 각 게시물의 개별 댓글을 검색해야 하는 시나리오를 상상해보세요. 프런트엔드 React 앱에서 세 개의 별도 API 호출을 만들고 불필요한 데이터를 처리하는 대신, GraphQL로 프로세스를 간소화할 수 있습니다. 스키마를 정의하고 GraphQL 쿼리를 작성함으로써 UI에서 필요로 하는 정확한 데이터만 요청할 수 있어 한 번에 모든 것을 효율적으로 요청할 수 있습니다.
다른 엔드포인트에서 사용자 데이터, 게시물 및 각 게시물의 댓글을 검색해야 합니다. 여러 엔드포인트에서 데이터를 수집하고 GraphQL을 통해 반환하기 위해 fetch를 사용할 것입니다.
리졸버 업데이트
const fetch = require('node-fetch');
const resolvers = {
Query: {
user: async (_, { id }) => {
try {
// fetch user
const userResponse = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const user = await userResponse.json();
// fetch posts for a user
const postsResponse = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${id}`);
const posts = await postsResponse.json();
// fetch comments for a post
const postsWithComments = await Promise.all(
posts.map(async (post) => {
const commentsResponse = await fetch(`https://jsonplaceholder.typicode.com/comments?postId=${post.id}`);
const comments = await commentsResponse.json();
return { post, comments };
})
);
return {
id: user.id,
name: user.name,
email: user.email,
posts: postsWithComments,
};
} catch (error) {
throw new Error(`Failed to fetch user data: ${error.message}`);
}
},
},
};
module.exports = resolvers;
GraphQL 스키마 업데이트
const { gql } = require('apollo-server');
const typeDefs = gql`
type Comment {
id: ID!
name: String
email: String
body: String
}
type Post {
id: ID!
title: String
body: String
comments: [Comment]
}
type User {
id: ID!
name: String
email: String
posts: [Post]
}
type Query {
user(id: ID!): User
}
`;
module.exports = typeDefs;
server.js
에서 서버 설정은 동일합니다. React.js 코드를 업데이트하면 아래 출력물을 얻게 됩니다:
결과
당신은 다음과 같은 상세한 사용자를 볼 수 있습니다: [Github 링크].
이 방법의 장점
React 애플리케이션에 GraphQL을 통합하면 여러 가지 이점이 있습니다:
오버페칭 제거
GraphQL의 중요한 기능 중 하나는 정확히 요청한 것만 가져온다는 점입니다. 서버는 요청된 필드만 반환하고 쿼리가 요구하는 것만 제공하여 네트워크를 통해 전송되는 데이터 양을 줄이므로 성능이 향상됩니다.
프론트엔드 코드 간소화
GraphQL은 원본에 관계없이 필요한 정보를 단일 쿼리로 가져올 수 있게 합니다. 내부적으로 정보를 가져오기 위해 3개의 API 호출을 할 수도 있습니다. 이는 프론트엔드 코드를 간소화하는 데 도움이 됩니다. 왜냐하면 이제 다른 비동기 요청을 조율하고 그 결과를 결합할 필요가 없기 때문입니다.
개발자 경험 향상
강력한 유형 지정 및 스키마 탐색은 전통적인 API 구현보다 더 나은 도구 지원과 오류 확인을 제공합니다. 또한, GraphiQL이나 Apollo Explorer와 같은 대화형 환경에서 개발자가 쿼리를 작성하고 테스트할 수 있습니다.
복잡성과 도전 과제 대응
이 방법은 일부 이점을 제공하지만 관리해야 할 도전 과제도 도입합니다.
추가 백엔드 계층
GraphQL 서버의 도입으로 백엔드 아키텍처에 추가적인 계층이 생기며, 제대로 관리되지 않으면 단일 장애 지점이 될 수 있습니다.
해결책
오류 처리와 모니터링에 주의를 기울이세요. Docker와 Kubernetes와 같은 컨테이너화 및 조율 도구를 사용하여 확장성과 신뢰성을 관리할 수 있습니다.
잠재적인 성능 오버헤드
GraphQL 서버는 단일 쿼리를 해결하기 위해 여러 REST API 호출을 할 수 있으며, 이는 시스템에 지연 시간과 오버헤드를 도입할 수 있습니다.
해결책
API에 여러 번 호출하지 않도록 결과를 캐싱하세요. DataLoader와 같은 도구를 사용하여 일괄 처리와 캐싱 요청을 처리할 수 있습니다.
결론
“간결함은 궁극의 세련미이다.” – 레오나르도 다 빈치
React 애플리케이션에 GraphQL을 통합하는 것은 성능 최적화 이상의 의미를 갖습니다. 이는 유지보수 가능하고 확장 가능하며 효율적인 애플리케이션을 구축하기 위한 전략적인 움직임입니다. 과다 요청 문제를 해결하고 데이터 관리를 간소화함으로써 사용자 경험을 향상시키는 것뿐만 아니라 개발팀이 더 나은 도구와 실천 방법을 활용할 수 있도록 돕습니다.
GraphQL 레이어를 도입하면 고유의 일련의 도전 과제가 따르지만, 혜택은 종종 복잡성을 상회합니다. 구현을 신중히 계획하고 리졸버를 최적화하며 엔드포인트를 보호함으로써 잠재적인 단점을 완화할 수 있습니다. 또한 GraphQL이 제공하는 유연성은 애플리케이션이 성장하고 발전함에 따라 미래를 대비할 수 있습니다.
GraphQL을 수용하는 것은 기존의 REST API를 포기한다는 것을 의미하지 않습니다. 대신, 프론트엔드 애플리케이션에 대한 효율적이고 유연한 데이터 액세스 레이어를 제공하는 동안 그들의 장점을 활용할 수 있도록 합니다. 이 하이브리드 접근 방식은 REST의 신뢰성과 GraphQL의 민첩성을 결합하여 양쪽의 장점을 누릴 수 있습니다.
React 애플리케이션을 다음 수준으로 이끌고 싶다면 데이터 가져오기 전략에 GraphQL을 통합해보세요. 이 과정은 도전을 안겨줄 수 있지만, 원활한 개발 프로세스, 더 행복한 개발자, 만족한 사용자들과 같은 보상들은 그것을 가치 있는 노력으로 만듭니다.
전체 코드 사용 가능
당신은 이 구현에 대한 전체 코드를 제 GitHub 저장소에서 찾을 수 있습니다.
Source:
https://dzone.com/articles/enhancing-react-applications-with-graphql-over-rest-apis