🌐 웹개발

GraphQL

그래프큐엘 (Graph Query Language)

GraphQL은 Facebook이 개발한 API 쿼리 언어로, 클라이언트가 필요한 데이터만 정확히 요청할 수 있습니다. REST의 Over-fetching과 Under-fetching 문제를 해결하며, 강력한 타입 시스템과 인트로스펙션을 제공합니다.

📖 상세 설명

GraphQL은 2012년 Facebook 내부에서 개발되어 2015년 오픈소스로 공개된 API 쿼리 언어이자 런타임입니다. 모바일 앱의 느린 네트워크 환경에서 최적화된 데이터 페칭을 위해 탄생했으며, REST API의 고질적인 문제들을 해결합니다. 현재 GitHub, Shopify, Twitter 등 대형 서비스에서 채택하고 있습니다.

핵심 개념은 스키마(Schema)입니다. SDL(Schema Definition Language)로 타입과 필드를 정의하면, 클라이언트는 그 스키마 안에서 원하는 필드만 선택해서 요청합니다. Over-fetching(불필요한 데이터 받기)과 Under-fetching(여러 번 요청해야 함)이 사라지고, 네트워크 효율이 크게 개선됩니다.

세 가지 연산 타입이 있습니다: Query(읽기), Mutation(쓰기/수정/삭제), Subscription(실시간 구독). Query는 데이터 조회, Mutation은 데이터 변경, Subscription은 WebSocket을 통한 실시간 업데이트를 담당합니다. 단일 엔드포인트(/graphql)에서 모든 연산을 처리합니다.

인트로스펙션(Introspection)은 GraphQL의 강력한 개발자 경험 기능입니다. 스키마 자체를 쿼리할 수 있어, GraphiQL이나 Apollo Sandbox 같은 도구가 자동완성, 문서화, 쿼리 테스트를 제공합니다. 프론트엔드와 백엔드 간 계약(Contract)이 명확해져 협업이 수월해집니다.

💻 코드 예제

# GraphQL 스키마 정의 (SDL)
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  published: Boolean!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  post(id: ID!): Post
}

type Mutation {
  createUser(name: String!, email: String!): User!
  createPost(title: String!, content: String!, authorId: ID!): Post!
  publishPost(id: ID!): Post!
}

# 클라이언트 쿼리 예시 - 필요한 필드만 요청
query GetUserWithPosts($userId: ID!) {
  user(id: $userId) {
    id
    name
    posts {
      id
      title
      published
    }
  }
}

# Mutation 예시
mutation CreateNewPost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    author {
      name
    }
  }
}

// JavaScript 클라이언트 (Apollo Client)
import { gql, useQuery, useMutation } from '@apollo/client';

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
      posts {
        id
        title
      }
    }
  }
`;

function UserProfile({ userId }) {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { id: userId },
  });

  if (loading) return <p>로딩 중...</p>;
  if (error) return <p>에러: {error.message}</p>;

  return (
    <div>
      <h1>{data.user.name}</h1>
      <ul>
        {data.user.posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

🗣️ 실무 대화 예시

👨‍💻 프론트엔드 개발자
"사용자 프로필에 최근 게시물 3개만 보여줘야 하는데, REST API가 사용자 정보랑 게시물을 따로 줘서 2번 호출해야 해요. 게시물 응답에는 필요 없는 필드도 너무 많고요."
👩‍💻 백엔드 개발자
"GraphQL로 전환하면 한 번의 요청에 user와 posts를 한꺼번에 가져올 수 있어요. limit 인자로 3개만 요청하고, 필요한 필드만 지정하면 응답도 깔끔해지죠. 스키마 한 번 정의해두면 프론트에서 필요한 대로 쿼리만 바꾸면 돼요."
👨‍💼 기술 면접관
"GraphQL의 N+1 문제는 어떻게 해결하시나요?"
👨‍💻 지원자
"DataLoader 패턴을 사용합니다. 각 resolver에서 개별 DB 쿼리 대신, DataLoader가 요청을 배치로 모아서 한 번에 처리해요. 예를 들어 10명의 author를 가져올 때, 10번의 쿼리가 아닌 WHERE id IN (...) 한 번으로 해결됩니다."

⚠️ 주의사항

🔗 관련 용어

📚 더 배우기