GraphQL API#
GraphQL은 API를 위한 쿼리 언어이자 서버 측에서 이러한 쿼리를 실행하기 위한 런타임이다. 2015년 Facebook(현 Meta)에서 공개한 후 오픈 소스 프로젝트로 발전했으며, API 개발 방식에 혁신을 가져왔다.
가장 단순하게 설명하자면, GraphQL은 클라이언트가 서버에게 “정확히 필요한 데이터만” 요청할 수 있게 해주는 기술이다. 이는 전통적인 REST API와는 다른 접근 방식을 제공한다.
여기서 ‘Graph’는 그래프 데이터 구조를 의미한다. GraphQL은 데이터를 노드(객체)와 엣지(관계)로 구성된 그래프로 모델링한다. ‘QL’은 Query Language의 약자로, 이 그래프 구조에 대한 쿼리를 작성하는 언어를 의미한다.
왜 GraphQL이 필요한가?#
전통적인 REST API의 한계#
REST API는 오랫동안 웹 서비스의 표준이었지만, 몇 가지 주요 한계가 있다:
- 오버페칭(Overfetching): 필요 이상의 데이터를 가져오는 문제이다. 예를 들어, 사용자 이름만 필요한데 전체 사용자 정보를 받게 되는 상황.
- 언더페칭(Underfetching): 필요한 모든 데이터를 얻기 위해 여러 번의 API 호출이 필요한 상황이다. 예를 들어, 사용자와 그 사용자의 게시물을 가져오려면 두 번의 별도 요청이 필요할 수 있다.
- 엔드포인트 증가: 다양한 클라이언트 요구를 충족하기 위해 점점 더 많은 엔드포인트가 생기는 문제이다.
- 버전 관리의 어려움: API가 변경되면 새로운 버전을 만들고 이전 버전과의 호환성을 유지해야 하는 복잡성이 있다.
GraphQL의 핵심 원칙#
- 단일 엔드포인트: REST와 달리 GraphQL은 일반적으로 단일 엔드포인트(예:
/graphql
)를 사용한다. - 클라이언트 주도적 데이터 요청: 클라이언트가 필요한 데이터만 정확히 요청할 수 있다.
- 강력한 타입 시스템: 모든 데이터 타입이 명확하게 정의되어 있다.
GraphQL의 해결책#
GraphQL은 이러한 문제들을 해결하기 위해 설계되었다:
- 필요한 데이터만 요청: 클라이언트가 정확히 필요한 필드만 명시하여 요청할 수 있다.
- 단일 요청으로 여러 리소스 가져오기: 하나의 쿼리로 여러 관련 데이터를 한 번에 가져올 수 있다.
- 단일 엔드포인트: 일반적으로
/graphql
이라는 하나의 엔드포인트만 사용한다. - 타입 시스템: 강력한 타입 시스템을 통해 API 변경 사항을 더 명확하게 관리할 수 있다.
GraphQL의 주요 구성 요소#
스키마 (Schema)#
GraphQL API의 핵심은 스키마이다. 스키마는 API에서 사용 가능한 모든 데이터 타입과 관계, 그리고 클라이언트가 실행할 수 있는 작업들을 정의한다.
간단한 블로그 API 스키마 예시:
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
| type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
}
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
}
type Query {
getPost(id: ID!): Post
getUser(id: ID!): User
getPosts: [Post!]!
}
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
createComment(text: String!, postId: ID!, authorId: ID!): Comment!
}
|
이 스키마에서:
Post
, User
, Comment
는 객체 타입이다.Query
는 데이터를 조회하는 작업을 정의한다.Mutation
은 데이터를 변경하는 작업을 정의한다.!
는 필수 필드(non-nullable)를 나타낸다.[Type]
은 해당 타입의 배열을 나타낸다.
타입 시스템#
GraphQL은 강력한 타입 시스템을 가지고 있으며 다음과 같은 기본 타입들이 있다:
- 스칼라 타입: Int, Float, String, Boolean, ID
- 객체 타입: 사용자 정의 타입 (User, Post 등)
- 열거형 타입: 특정 값 세트
- 인터페이스와 유니온 타입: 타입 간의 공통점을 표현하는 추상 타입
- 입력 타입: 변이(mutation)에 인자로 전달되는 복잡한 객체
작업 유형 (Operation Types)#
GraphQL에는 세 가지 주요 작업 유형이 있다:
- 쿼리(Query): 데이터 읽기 작업 (GET과 유사)
- 뮤테이션(Mutation): 데이터 변경 작업 (POST, PUT, DELETE와 유사)
- 구독(Subscription): 실시간 데이터 업데이트 (웹소켓과 유사)
리졸버 (Resolver)#
리졸버는 GraphQL 필드의 데이터를 어떻게 가져올지 정의하는 함수이다.
각 필드마다 연결된 리졸버가 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 리졸버 예제 (JavaScript)
const resolvers = {
Query: {
user: (parent, args, context, info) => {
// args.id를 사용하여 사용자 검색
return database.findUserById(args.id);
}
},
User: {
posts: (parent, args, context, info) => {
// parent.id를 사용하여 사용자의 게시물 검색
return database.findPostsByAuthorId(parent.id);
}
}
};
|
기타 특징#
인트로스펙션(Introspection)#
GraphQL은 인트로스펙션이라는 강력한 기능을 제공한다.
이를 통해 API 스스로가 자신이 제공하는 타입과 쿼리에 대한 정보를 노출할 수 있다.
1
2
3
4
5
6
7
8
| {
__schema {
types {
name
description
}
}
}
|
이런 쿼리를 통해 API의 모든 타입 목록을 가져올 수 있으며, 이는 문서화와 개발 도구에서 매우 유용하다.
프래그먼트(Fragments)#
프래그먼트는 재사용 가능한 필드 집합으로, 쿼리를 단순화하는 데 도움이 된다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fragment UserDetails on User {
id
name
email
}
{
getUser(id: "1") {
...UserDetails
posts {
title
}
}
}
|
별칭(Aliases)#
같은 필드를 다른 인자로 여러 번 쿼리하려면 별칭을 사용할 수 있다:
1
2
3
4
5
6
7
8
| {
firstPost: getPost(id: "1") {
title
}
secondPost: getPost(id: "2") {
title
}
}
|
디렉티브(Directives)#
쿼리의 실행 방식을 동적으로 변경하는 주석과 같은 기능이다:
1
2
3
4
5
6
7
| {
getUser(id: "1") {
name
email @include(if: $includeEmail)
phoneNumber @skip(if: $hidePhoneNumber)
}
}
|
여기서 @include
와 @skip
은 내장 디렉티브로, 조건에 따라 필드를 포함하거나 제외한다.
GraphQL 쿼리 작성법#
기본 쿼리#
1
2
3
4
5
6
7
8
9
| query {
user(id: "123") {
name
email
posts {
title
}
}
}
|
변수와 인자#
1
2
3
4
5
6
7
8
9
10
11
| query GetUser($id: ID!) {
user(id: $id) {
name
email
}
}
# 변수
{
"id": "123"
}
|
뮤테이션#
1
2
3
4
5
6
7
8
9
| mutation CreatePost($title: String!, $content: String!, $authorId: ID!) {
createPost(title: $title, content: $content, authorId: $authorId) {
id
title
author {
name
}
}
}
|
GraphQL 고급 기능#
페이지네이션#
대량의 데이터를 처리할 때 페이지네이션은 필수적이다.
GraphQL에서는 주로 두 가지 접근 방식을 사용한다:
오프셋 기반 페이지네이션:
1
2
3
4
5
6
| {
getPosts(offset: 0, limit: 10) {
id
title
}
}
|
커서 기반 페이지네이션:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| {
getPosts(first: 10, after: "cursor_value") {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
|
데이터 캐싱#
GraphQL 클라이언트(Apollo Client, Relay 등)는 쿼리 결과를 효율적으로 캐싱하는 기능을 제공한다:
1
2
3
4
5
6
7
8
9
10
11
| // Apollo Client의 캐시 설정
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache({
typePolicies: {
User: {
keyFields: ['id'], // 개체 식별을 위한 필드
}
}
})
});
|
실시간 업데이트: 구독(Subscriptions)#
WebSocket을 통한 실시간 데이터 업데이트를 위해 GraphQL은 구독을 제공한다:
1
2
3
4
5
6
7
8
9
| subscription {
newComment(postId: "123") {
id
text
author {
name
}
}
}
|
서버 측 구현:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();
const resolvers = {
Subscription: {
newComment: {
subscribe: (_, { postId }) =>
pubsub.asyncIterator([`NEW_COMMENT_${postId}`])
}
},
Mutation: {
createComment: (_, { text, postId, authorId }) => {
// 댓글 생성 로직...
// 새 댓글 이벤트 발생
pubsub.publish(`NEW_COMMENT_${postId}`, {
newComment: newComment
});
return newComment;
}
}
};
|
배치 요청#
여러 쿼리를 배치 처리할 수 있어 네트워크 요청 수를 줄일 수 있다:
1
2
3
4
5
6
7
8
9
10
11
12
13
| // Apollo Client에서의 배치 요청
import { BatchHttpLink } from '@apollo/client/link/batch';
const link = new BatchHttpLink({
uri: 'http://localhost:4000/graphql',
batchMax: 5, // 최대 배치 크기
batchInterval: 20 // 배치 간격 (ms)
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
|
GraphQL 보안 및 최적화#
N+1 문제#
GraphQL에서 자주 발생하는 성능 문제 중 하나는 N+1 쿼리 문제이다.
예를 들어, 10개의 게시물을 가져오는 쿼리에서 각 게시물의 작성자 정보를 가져오기 위해 추가로 10번의 데이터베이스 쿼리가 발생할 수 있다.
이 문제를 해결하기 위한 방법으로는 다음과 같은 것들이 있다:
데이터로더(DataLoader): 중복 요청을 배치 처리하여 효율성을 높인다.
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 DataLoader = require('dataloader');
// 사용자 로더 생성
const userLoader = new DataLoader(async (userIds) => {
console.log('로드할 사용자 ID들:', userIds);
// 단일 쿼리로 여러 사용자 로드
const users = await UserModel.find({ _id: { $in: userIds } });
// DataLoader는 userIds와 동일한 순서로 결과를 반환해야 함
const userMap = {};
users.forEach(user => {
userMap[user._id] = user;
});
return userIds.map(id => userMap[id]);
});
// 리졸버에서 사용
const resolvers = {
Post: {
author: async (post, _, { loaders }) => {
return await loaders.user.load(post.authorId);
}
}
};
|
쿼리 최적화: 특정 쿼리 패턴에 대해 최적화된 데이터베이스 쿼리를 작성한다.
인증 및 권한 부여#
GraphQL API에서 인증과 권한 관리는 중요한 부분이이다:
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
| // Apollo Server에서의 인증 컨텍스트
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
// 요청 헤더에서 토큰 추출
const token = req.headers.authorization || '';
// 토큰 검증 및 사용자 정보 추출
const user = validateToken(token);
return { user };
}
});
// 리졸버에서 권한 확인
const resolvers = {
Mutation: {
createPost: (_, { title, content }, { user }) => {
// 인증 확인
if (!user) {
throw new AuthenticationError('로그인이 필요합니다');
}
// 권한 확인
if (!user.hasPermission('create:post')) {
throw new ForbiddenError('게시물 생성 권한이 없습니다');
}
// 게시물 생성 로직...
}
}
};
|
쿼리 복잡성 제한#
악의적이거나 비효율적인 쿼리로부터 서버를 보호하기 위해 쿼리 복잡성을 제한할 수 있다:
1
2
3
4
5
6
7
8
9
| const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
createComplexityLimitRule(1000) // 최대 쿼리 복잡성 제한
]
});
|
속도 제한(Rate Limiting)#
API 남용을 방지하기 위한 속도 제한도 구현할 수 있다:
1
2
3
4
5
6
7
8
9
10
11
12
13
| const { createRateLimitRule } = require('graphql-rate-limit');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
createRateLimitRule({
identifyContext: (context) => context.user?.id,
windowMs: 60 * 1000, // 1분
max: 100 // 최대 100 요청
})
]
});
|
REST API vs. GraphQL#
REST API | GraphQL |
---|
여러 엔드포인트 | 단일 엔드포인트 |
오버페칭/언더페칭 문제 | 필요한 데이터만 요청 가능 |
버전 관리 필요 | 점진적 진화 가능 |
캐싱 내장 | 수동 캐싱 구현 필요 |
GraphQL vs. REST: 실제 예시로 비교하기#
REST와 GraphQL의 차이를 실제 상황을 통해 이해해보자.
블로그 앱에서 특정 게시물, 그 작성자 정보, 그리고 해당 게시물의 댓글들을 가져오고 싶다고 가정해보면:
REST 방식#
REST API를 사용하면 다음과 같은 여러 요청이 필요할 수 있다:
- 게시물 가져오기:
GET /posts/123
- 작성자 정보 가져오기:
GET /users/456
- 댓글 가져오기:
GET /posts/123/comments
각 요청은 해당 리소스의 모든 필드를 반환할 수 있으며, 이 중 일부만 필요한 경우에도 전체 데이터를 받게 된다.
GraphQL 방식#
GraphQL을 사용하면 단일 요청으로 필요한 모든 데이터를 가져올 수 있다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| {
getPost(id: "123") {
title
content
author {
name
email
}
comments {
text
author {
name
}
}
}
}
|
이 하나의 쿼리로 게시물, 작성자, 댓글 정보 중 필요한 특정 필드만 한 번에 가져올 수 있다.
주요 GraphQL 도구 및 라이브러리#
GraphQL 생태계는 다양한 도구와 라이브러리를 제공한다.
서버 측 라이브러리#
- Apollo Server: 가장 인기 있는 GraphQL 서버 구현체로, Express, Fastify 등 다양한 Node.js 프레임워크와 통합된다.
- Graphql-js: Facebook에서 개발한 공식 JavaScript 참조 구현체이다.
- TypeGraphQL: TypeScript로 GraphQL 스키마를 정의할 수 있게 해주는 프레임워크이다.
- Nexus: 코드 우선 접근 방식의 GraphQL 스키마 정의 라이브러리이다.
클라이언트 측 라이브러리#
- Apollo Client: 가장 널리 사용되는 GraphQL 클라이언트로, React, Vue, Angular 등 다양한 프레임워크와 통합된다.
- Relay: Facebook에서 개발한 GraphQL 클라이언트로, 성능 최적화에 중점을 둔다.
- urql: 경량화된 GraphQL 클라이언트로, 커스터마이징이 쉽다.
개발 도구#
- GraphQL Playground/GraphiQL: API를 탐색하고 테스트할 수 있는 브라우저 기반 IDE.
- Apollo Studio (이전 Apollo Graph Manager): GraphQL API 개발, 모니터링, 분석을 위한 통합 플랫폼.
- PostGraphile: PostgreSQL 데이터베이스로부터 자동으로 GraphQL API를 생성한다.
- Hasura: PostgreSQL, MS SQL Server 등의 데이터베이스에서 실시간 GraphQL API를 자동 생성한다.
- Prisma: 데이터베이스 ORM으로, GraphQL API 개발을 단순화한다.
GraphQL 실제 구현 방법#
GraphQL API를 구현하는 방법을 단계별로 살펴보자.
(JavaScript와 Node.js 환경을 기준)
1. 서버 설정 (Node.js + Express + Apollo Server)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const { typeDefs } = require('./schema');
const { resolvers } = require('./resolvers');
async function startServer() {
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
});
await server.start();
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () =>
console.log(`서버 실행 중: http://localhost:4000${server.graphqlPath}`)
);
}
startServer();
|
2. 스키마 정의#
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
| // schema.js
const { gql } = require('apollo-server-express');
const typeDefs = gql`
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
}
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
}
type Query {
getPost(id: ID!): Post
getUser(id: ID!): User
getPosts: [Post!]!
}
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
createComment(text: String!, postId: ID!, authorId: ID!): Comment!
}
`;
module.exports = { typeDefs };
|
3. 리졸버 구현#
리졸버는 GraphQL 쿼리에 대한 실제 데이터를 가져오는 함수:
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
56
57
58
59
60
61
62
63
64
| // resolvers.js
// 간단한 예시를 위한 인메모리 데이터
const posts = [
{ id: '1', title: 'GraphQL 소개', content: 'GraphQL은 API를 위한 쿼리 언어입니다...', authorId: '1' }
];
const users = [
{ id: '1', name: '홍길동', email: 'hong@example.com' }
];
const comments = [
{ id: '1', text: '좋은 글이네요!', authorId: '1', postId: '1' }
];
const resolvers = {
// 객체 간 관계 해결
Post: {
author: (post) => users.find(user => user.id === post.authorId),
comments: (post) => comments.filter(comment => comment.postId === post.id)
},
User: {
posts: (user) => posts.filter(post => post.authorId === user.id)
},
Comment: {
author: (comment) => users.find(user => user.id === comment.authorId),
post: (comment) => posts.find(post => post.id === comment.postId)
},
// 쿼리 리졸버
Query: {
getPost: (_, { id }) => posts.find(post => post.id === id),
getUser: (_, { id }) => users.find(user => user.id === id),
getPosts: () => posts
},
// 뮤테이션 리졸버
Mutation: {
createPost: (_, { title, content, authorId }) => {
const newPost = {
id: String(posts.length + 1),
title,
content,
authorId
};
posts.push(newPost);
return newPost;
},
createComment: (_, { text, postId, authorId }) => {
const newComment = {
id: String(comments.length + 1),
text,
authorId,
postId
};
comments.push(newComment);
return newComment;
}
}
};
module.exports = { resolvers };
|
4. 클라이언트 측 구현 (React + Apollo Client)#
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
| // App.js
import React from 'react';
import { ApolloClient, InMemoryCache, ApolloProvider, useQuery, gql } from '@apollo/client';
// Apollo 클라이언트 설정
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql',
cache: new InMemoryCache()
});
// 게시물 쿼리 정의
const GET_POST = gql`
query GetPost($id: ID!) {
getPost(id: $id) {
title
content
author {
name
}
comments {
text
author {
name
}
}
}
}
`;
// 게시물 컴포넌트
function Post({ id }) {
const { loading, error, data } = useQuery(GET_POST, {
variables: { id }
});
if (loading) return <p>로딩 중...</p>;
if (error) return <p>오류: {error.message}</p>;
const post = data.getPost;
return (
<div>
<h1>{post.title}</h1>
<p>작성자: {post.author.name}</p>
<div>{post.content}</div>
<h2>댓글</h2>
{post.comments.map(comment => (
<div key={comment.id}>
<p>{comment.text}</p>
<small>작성자: {comment.author.name}</small>
</div>
))}
</div>
);
}
// 앱 컴포넌트
function App() {
return (
<ApolloProvider client={client}>
<div className="App">
<Post id="1" />
</div>
</ApolloProvider>
);
}
export default App;
|
용어 정리#
용어 | 설명 |
---|
오버페칭(Overfetching) | 오버페칭은 클라이언트가 요청한 데이터보다 불필요하게 많은 데이터를 서버로부터 받아오는 상황을 의미한다. 이로 인해 네트워크 리소스가 낭비되고 클라이언트 측에서 불필요한 데이터를 처리해야 하므로 성능 저하가 발생할 수 있다.
예시: - 클라이언트가 사용자 이름만 필요하지만 API가 사용자 이름, 이메일, 주소 등 모든 정보를 반환하는 경우. - 예를 들어, GET /users 요청 시 각 사용자에 대한 이름, 나이, 주소, 전화번호 등이 반환되지만 클라이언트는 이름과 이메일만 필요한 상황.
문제점: - 네트워크 비용 증가: 불필요한 데이터 전송으로 인해 네트워크 트래픽이 증가. - 응답 시간 증가: 과도한 데이터 처리로 인해 응답 시간이 길어짐. - 클라이언트 처리 부담: 사용하지 않는 데이터를 필터링하거나 무시해야 하는 추가 작업 필요. |
언더페칭(Underfetching) | 언더페칭은 클라이언트가 요청한 데이터가 충분하지 않아 추가적인 요청을 해야 하는 상황을 의미한다. 이는 하나의 엔드포인트로 필요한 모든 데이터를 가져오지 못해 여러 번의 API 호출이 필요하게 된다.
예시: - 클라이언트가 사용자 이름과 친구 목록을 동시에 필요로 하지만 API가 사용자 이름만 반환하는 경우. 추가적으로 친구 목록을 가져오기 위해 별도의 요청이 필요. - 예를 들어, GET /user 요청으로 사용자 ID만 받고, 친구 목록을 얻기 위해 추가적으로 GET /user/:id/friends 를 호출해야 하는 상황.
문제점: - 추가 요청 증가: 여러 번의 API 호출로 인해 네트워크 트래픽이 증가. - 응답 지연: 필요한 데이터를 모두 얻기까지 시간이 더 오래 걸림. - 복잡한 코드 작성 필요: 클라이언트 측에서 여러 요청을 관리하고 결합해야 함. |
인트로스펙션(Introspection) | GraphQL 서버가 자신의 스키마(schema), 타입(types), 필드(fields), 그리고 관계를 클라이언트가 쿼리할 수 있도록 하는 기능을 의미한다. 이를 통해 개발자는 API의 구조를 탐색하고 필요한 데이터를 효율적으로 요청할 수 있다. 인트로스펙션은 GraphQL의 핵심적인 특징 중 하나로, API를 **자체 문서화(self-documenting)**하고 개발자 경험을 크게 향상시킨다. |
참고 및 출처#
GraphQL API vs. GraphQL GraphQL과 GraphQL API는 현대 웹 개발에서 자주 언급되는 개념이지만, 이 둘 사이에는 명확한 차이점이 있다.
GraphQL과 GraphQL API의 개념적 차이 GraphQL은 페이스북이 2012년에 개발하고 2015년에 오픈소스로 공개한 쿼리 언어와 서버 측 런타임 사양(specification)이다. 반면 GraphQL API는 이 GraphQL 사양을 구현한 실제 API 인터페이스를 의미한다.
쉽게 설명하자면, GraphQL은 프로그래밍 언어인 SQL과 같은 개념이고, GraphQL API는 이 언어를 사용하여 구축된 실제 데이터베이스 인터페이스와 같다.
핵심 특징 비교 GraphQL은 클라이언트가 정확히 필요한 데이터만 요청할 수 있는 유연한 쿼리 언어를 제공한다.
이는 REST API에서 흔히 발생하는 오버페칭(over-fetching)과 언더페칭(under-fetching) 문제를 해결한다.
...