eatthefrog

GraphQL 핵심 요약 본문

백엔드 노트

GraphQL 핵심 요약

eater_forg 2025. 6. 18. 13:52

목차

  1. GraphQL의 정의 및 역할
  2. GraphQL API 구조 (스키마 및 리졸버)
  3. 스키마의 구성 요소 (쿼리, 뮤테이션, 서브스크린, 타입)
  4. 리졸버와 리졸버 체이닝
  5. 프런트엔드와 백엔드 간의 GraphQL 통신 방식

 

 

 

 

1. GraphQL 정의

GraphQL은 기술 라이브러리나 데이터베이스가 아닌 API를 요청하는 쿼리 언어다. 이는 프런트엔드와 백엔드 사이에 존재하는 계층으로서 서버에서 클라이언트로 보내는 데이터를 정의된 형식으로 노출하는 역할을 한다.


2. GraphQL API의 구성 요소

GraphQL API는 크게 스키마(Schema)리졸버(Resolvers)로 나뉜다.

 

BASICS

- SCHEMA : API 작동 방식 / 타입: QUERY, MUTATION, Subscription

- QUERY : FETCHING, READING

- METATION : CREATING, UPDATING, DELETING

- RESOLVERS : 백엔드에서 실제 데이터 처리 함수/ 스키마에 정의된 작업 실행


 

3.  스키마의 구성 요소

스카마(Schema)

스키마는 기본적으로 API가 어떻게 작동할지 성명하는 방법입다. 여기에는 API가 어떤 종류의 데이터를 받을지, 보낼지, 변경할지에 대한 정보가 포함된다.


 

a. 중요한 특징

  • 스키마는 데이터베이스 스키마와는 독립적이다
  • 스키마 정의 언어(SDL)이라는 특별한 문법을 사용한다.
  • 모든 스키아에는 세 가지 루트 타입이 있다: Query, Mutation, Subscription

 

b. 루트 타입들 개념

- Query Type: API 에서 데이터를 읽고 가져오는데 사용된다. (CRUD의 Read에 해당)
- Mutation Type: API에서 데이터를 생성, 업데이트 또는 삭제하는데 사용된다. (CRUD의 Create, Update, Delete에 해당)
- Subscription Type: 실시간 데이터 업데이트를 위해 사용된다. (특정 이벤트를 구독하여 실시간으로 데이터 변경사항을 받을 수 있다.)

 

c. 루트 타입들 작성 예시

## Query Type
type Query {
  user(id: ID!): User
  posts: [Post!]!
}

## Mutation Type
type Mutation {
  createUser(input: UserInput!): User
  updateUser(id: ID!, input: UserInput!): User
  deleteUser(id: ID!): Boolean
}

## Subscription Type
type Subscription {
  messageAdded: Message
  userOnline: User
  postUpdated(postId: ID!): Post
}

 

 

d. 타입 시스템 : User과 같이 사용자 정의 데이터 구조를 저의하며, 필드와 해당 타입을 지정할 수 있다.

type User {
  id: ID!              # 필수 필드 (느낌표로 표시)
  name: String!        # 필수 문자열
  email: String        # 선택적 문자열
  posts: [Post!]!      # Post 배열 (각 Post는 null이 아니며, 배열 자체도 null이 아님)
  age: Int             # 선택적 정수
  isActive: Boolean!   # 필수 불린값
}

## 기본 타입: String, Int, Boolean, Float, ID
## Non-null 표기: ! (필수 필드)
## 배열 표기: []
## 중첩된 Non-null: [Post!]! (Post 배열이며, 각 Post는 null이 아니고, 배열 자체도 null이 아님)

 

e. 인자 (Arguments) : 특정 쿼리나 뮤테이션에 필요한 정보를 프론트엔드로부터 받을 수 있도록 인자를 정의할 수 있다.

type Query {
  user(id: ID!): User                    # id를 인자로 받음
  posts(limit: Int, offset: Int): [Post!]!  # 페이지네이션 인자
}

f. Input Type : 뮤테이션을 통해 데이터를 생성하거나 업데이트 할 때, 프런트에서 백엔드로 전달될 데이터 구조를 정의하는데 사용

input UserInput {
  name: String!
  email: String!
  age: Int
}

 

 


 

4  리졸버와 리졸버 체이닝

리졸버 (Resolvers)

리졸버는 스키마에서 정의된 각 필드에 대해 실제로 데이터를 가져오거나 변경하는 백엔드 함수다.


 

a. 리졸버의 특징

  • REST API의 엔드포인트보다 더 세분화되어 있다.
  • 각 필드마다 개별 리졸버 함수를 가질 수 있다.
  • 리졸버 체이닝: 상위 리졸버의 결과가 하위 리졸버의 입력으로 전달된다.

 b. 리졸버 함수의 구조: 모든 리졸버 함수는 네 개의 매개변수를 받는다.

const resolvers = {
  Query: {
    user: (parent, args, context, info) => {
      // parent: 상위 리졸버의 결과
      // args: 쿼리에서 전달된 인자들
      // context: 요청 컨텍스트 (인증 정보 등)
      // info: 쿼리 메타데이터
      return getUserById(args.id);
    }
  },
  
  User: {
    posts: (user, args, context, info) => {
      // user 객체는 상위 Query.user 리졸버에서 전달됨
      return getPostsByUserId(user.id);
    }
  }
};

 

c. 리졸버 체이닝 예시

// 1. Query.user 실행 → User 객체 반환
// 2. User.posts 실행 → 위에서 반환된 User 객체를 받아 posts 조회
// 3. Post.author 실행 → 각 Post 객체를 받아 author 조회

5. 프런트엔드와 백엔드 간의 GraphQL 통신 방식

GraphQL은 일반적으로 단일 엔드포인트(/graphql)을 통해 모든 통신을 처리한다. REST API와 달리 여러 URL이 아닌 하나의 URL로 모든 데이터 요청을 보낸다.

 

1) 기본 통신 구조

// 프런트엔드에서 GraphQL 쿼리 전송
const response = await fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: `
      query {
        user(id: "123") {
          name
          email
          posts {
            title
            content
          }
        }
      }
    `
  })
});

const data = await response.json();

2) 변수를 사용한 통신

// 쿼리와 변수를 분리하여 전송
const query = `
  query GetUser($userId: ID!) {
    user(id: $userId) {
      name
      email
      posts {
        title
        content
      }
    }
  }
`;

const variables = { userId: "123" };

const response = await fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: query,
    variables: variables
  })
});

3) 클라이어트 라이브러리를 통한 통신 ( Apollo Client + React 예시)

import { useQuery, gql } from '@apollo/client';

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

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

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
    </div>
  );
}

4) GraphQL Request(간단한 라이브러리)

import { request, gql } from 'graphql-request';

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

const data = await request('/graphql', query, { userId: "123" });

 

5) Mutation을 통한 데이터 변경

// 데이터 생성/수정/삭제
const mutation = `
  mutation CreateUser($input: UserInput!) {
    createUser(input: $input) {
      id
      name
      email
    }
  }
`;

const variables = {
  input: {
    name: "김철수",
    email: "kim@example.com"
  }
};

const response = await fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: mutation,
    variables: variables
  })
});

6) Subscription을 통한 실시간 통신

// WebSocket을 통한 실시간 데이터 구독
import { useSubscription, gql } from '@apollo/client';

const MESSAGE_SUBSCRIPTION = gql`
  subscription OnMessageAdded {
    messageAdded {
      id
      content
      user {
        name
      }
    }
  }
`;

function ChatRoom() {
  const { data, loading } = useSubscription(MESSAGE_SUBSCRIPTION);

  if (loading) return <p>Loading...</p>;

  return (
    <div>
      <p>새 메시지: {data?.messageAdded?.content}</p>
    </div>
  );
}

응답 형태

GraphQL의 모든 응답은 일관된 JSON 구조를 가진다.

1) 성공적인 응답

{
  "data": {
    "user": {
      "name": "김철수",
      "email": "kim@example.com",
      "posts": [
        {
          "title": "첫 번째 글",
          "content": "GraphQL 학습 중입니다."
        }
      ]
    }
  }
}

2) 에러가 포함된 응답

{
  "data": {
    "user": null
  },
  "errors": [
    {
      "message": "User not found",
      "locations": [{"line": 2, "column": 3}],
      "path": ["user"]
    }
  ]
}

'백엔드 노트' 카테고리의 다른 글

MongoDB Compass indexes  (0) 2025.11.11
CI/CD 개발 프로세스  (0) 2025.06.24
GraphQL: A query language for your API 공식문서 읽기  (2) 2025.06.18
Apollo Server로 GraphQL API 만들기  (0) 2025.06.16
REST API의 한계와 GraphQL  (2) 2025.06.16