GraphQL의 가장 큰 강점 중 하나는 정적 타입 시스템 기반의 명확한 구조이다. 서버에서 스키마(Schema)를 정의하고, 클라이언트는 그 구조에 따라 안전하게 쿼리를 작성할 수 있다.

 

GraphQL 스키마란?


GraphQL 스키마는  API에서 제공하는 모든 기능과 데이터 구조를 정의하는 설계도이다.

즉, 어떤 데이터를 어떻게 요청하고, 어떤 형식으로 응답받을지를 명시하는 계약서 역할을 한다.

 

📌 스키마는 쿼리, 뮤테이션, 타입, 디렉티브 등을 포함하여 구성된다.

 

 

GraphQL 스키마 구성 요소

  1. 타입 정의(Type Definition): 데이터 구조를 정의
  2. 쿼리 및 뮤테이션(Query and Mutation): 데이터 읽기/쓰기 정의
  3. 디렉티브(Directives): 실행 방식 제어

 

스키마는 다음을 정의한다.

  • 어떤 데이터 타입이 존재하는지
  • 어떤 쿼리(Mutation/Subscription)를 호출할 수 있는지
  • 각 필드가 어떤 타입을 반환하는지
  • 어떤 인자를 받을 수 있는지

 


🧩 GrphQL 타입 시스템 소개


GraphQL은 타입 시스템을 기반으로 쿼리와 뮤테이션을 수행한다. 모든 데이터 요소는 특정 타입에 속하며, 타입은 필드와 리졸버를 가진다.

 

타입의 종류

  • 스칼라 타입 (Scalar Types)
  • 객체 타입 (Object Types)
  • 인터페이스 타입 (Interface Types)
  • 유니온 타입 (Union Types)
  • 열거형 타입 (Enum Types)
  • 입력 객체 타입 (Input Object Types)

 

🔸 스칼라 타입 (Scalar Types)

가장 기본적인 타입으로 단일 값을 나타낸다.

타입 설명
String 문자열
Int 32비트 정수
Float 부동 소수점 숫자
Boolean true 또는 false
ID 고유 식별자 (보통 문자열로 처리)
type User {
  id: ID!
  name: String!
  age: Int
  verified: Boolean
}

 

🔸 객체 타입 (Object Types)

GraphQL에서 복잡한 데이터를 표현하는 핵심 타입으로, 하나 이상의 필드를 포함하며, 각 필드는 또 다른 타입을 가질 수 있다.

type Book {
  id: ID!
  title: String!
  author: Author!
}

type Author {
  id: ID!
  name: String!
  books: [Book]
}

 

🔸 인터페이스 타입 (Interface Types)

공통 필드를 정의하는 추상 타입으로, 여러 타입이 이를 구현할 수 있으며, 여러 타입을 일반화할 때 사용한다.

 

인터페이스를 구현하는 객체는 해당 인터페이스의 모든 필드를 구현해야 한다.

interface Character {
  id: ID!
  name: String!
}

type Hero implements Character {
  id: ID!
  name: String!
  power: String
}

 

🔸 유니온 타입 (Union Types)

하나의 필드가 여러 서로 다른 타입 중 하나를 반환할 수 있을 때 사용한다.

union SearchResult = Book | Author

type Query {
  search(keyword: String!): [SearchResult]
}

 

🔸 열거형 타입 (Enum Types)

제한된 값의 집합을 표현할 수 있는 타입이다.

enum BookStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

type Book {
  id: ID!
  title: String!
  status: BookStatus!
}

 

🔸 입력 객체 타입 (Input Object Types)

Mutation 같은 데이터 변경 작업에서 입력값을 명시적으로 정의할 수 있는 타입이다.

input CreateBookInput {
  title: String!
  authorId: ID!
}

mutation {
  createBook(input: CreateBookInput!): Book
}

 

🔁  List와 Non-Null 타입

books: [Book!]! // Book 배열이며, null 없음
  • List: 배열 형태의 값 (예: [Book])
  • Non-Null: ! 기호를 통해 null을 허용하지 않음

 

📌 전체 예제 스키마
type Query {
  getBook(id: ID!): Book
  search(keyword: String!): [SearchResult]
}

type Mutation {
  createBook(input: CreateBookInput!): Book
}

type Book {
  id: ID!
  title: String!
  author: Author!
  status: BookStatus!
}

type Author {
  id: ID!
  name: String!
  books: [Book]
}

enum BookStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

input CreateBookInput {
  title: String!
  authorId: ID!
}

union SearchResult = Book | Author

스키마 확장 (Schema Extension)


GraphQL은 매우 유연한 구조로, 기존 스키마에 새로운 타입이나 필드를 추가하는 것이 가능하다.

 

새로운 타입 추가

type Mutation {
  createUser(name: String!): User
}

type User {
  id: ID!
  name: String!
}

 

기존 타입 확장

extend type User {
  age: Int
}

 


사용자 정의 스칼라 타입 (Custom Scalar Types)


GraphQL 기본 타입 외에도 사용자 정의 스칼라 타입을 만들 수 있다.

 

Custom Date Type

scalar Date

type Event {
  id: ID!
  title: String!
  startAt: Date!
}
  • 서버에서 Date 타입에 대해 파싱/직렬화 로직을 직접 구현해야 한다.
import graphql.language.StringValue;
import graphql.schema.*;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class DateScalar {

    public static final GraphQLScalarType DATE = GraphQLScalarType.newScalar()
            .name("Date")
            .description("Custom scalar for LocalDate")
            .coercing(new Coercing<LocalDate, String>() {
                private final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;

                @Override
                public String serialize(Object dataFetcherResult) {
                    return ((LocalDate) dataFetcherResult).format(formatter);
                }

                @Override
                public LocalDate parseValue(Object input) {
                    return LocalDate.parse(input.toString(), formatter);
                }

                @Override
                public LocalDate parseLiteral(Object input) {
                    if (input instanceof StringValue) {
                        return LocalDate.parse(((StringValue) input).getValue(), formatter);
                    }
                    throw new CoercingParseLiteralException("Invalid date format");
                }
            })
            .build();
}

 

Custom Email Type

scalar Email

type User {
  id: ID!
  email: Email!
}
  • 이메일 포맷 검증을 추가할 수 있어 데이터 무결성 향상에 도움을 준다.
📌 커스텀 스칼라 타입은 GraphQLScalarType을 통해 정의되며, 유효성 검사 로직 포함이 가능하다.