어서와, 개발은 처음이지?

GraphQL Schema의 기본 문법 본문

GraphQL

GraphQL Schema의 기본 문법

오지고지리고알파고포켓몬고 2018. 10. 24. 00:29
반응형


이번 시간에는 GraphQL Schema의 기본적인 문법에 대해서 알아보겠습니다.

만약 GraphQL을 처음 접하신다면 먼저 이전 글(expressgraphql-시작하기)을 따라해보시고 읽어보시기 바랍니다.



1. 스칼라 타입(Scalar Types)


스키마에 사용할 수 있는 기본 자료형은 다음과 같습니다.


1. Int : 32비트 정수

2. Float : 실수

3. String : UTF-8 문자열

4. Boolean : true / false

5. ID : id 값임을 명시적으로 표현하기 위해 사용. 내부적으로는 String 형태와 동일



2. 객체 타입과 필드(Object types and Fields)


여러가지 스칼라 타입을 갖는 Field들을 하나 객체 형태로 묶을 수 있는데, 이를 객체 타입이라고 합니다.

자바스크립트의 객체 {key: value}와 유사한 모습을 가지고 있습니다.


쇼핑몰 서비스를 가정하여 제품id, 제품명, 가격, 수량, 해외배송 정보를 갖는 객체 타입을 정의해보겠습니다.


buildSchema를 사용하면 아래와 같이 스키마를 작성할 수 있습니다.

var { buildSchema } = require('graphql');

var schema = buildSchema(`
  type Query {
    product: Product
  }

  type Product {
    id: ID
    name: String
    price: Float
    volume: Int
    overseas: Boolean
  }
`);


GraphQLSchema를 사용한다면 아래와 같이 작성할 수 있습니다.

var Graphql = require('graphql');

const productType = new Graphql.GraphQLObjectType({
  name: "Product",
  fields:{
    id: { type: Graphql.GraphQLID },
    name: { type: Graphql.GraphQLString },
    price: { type: Graphql.GraphQLFloat },
    volume: { type: Graphql.GraphQLInt },
    overseas: { type: Graphql.GraphQLBoolean }
  }
});


물품 정보들이 Product 객체에 묶여있고, 각 필드는 스칼라 타입으로 자료형을 명시해 둔 모습을 볼 수 있습니다.

이는 실질적으로 DB에 있는 테이블 혹은 컬렉션의 스키마와 동일한 구조를 이룰 것 입니다.


위 스키마에 의해 데이터는 아래 같은 모습으로 표현됩니다.

<product의 요청과 응답>


3. 배열 표현(Array)


buildSchema에서는 객체 타입이나 스칼라에 []를 씌우는 것으로 배열임을 명시할 수 있습니다.

var schema = buildSchema(`
  type Query {
    products: [Product]
  }

  type Product {
    id: ID
    name: [String]
    price: Float
    volume: Int
    overseas: Boolean
  }
`);


[Product]는 연속된(배열) 형태의 Product 타입의 값이 출력된다는 뜻입니다.

스칼라 타입 역시 배열 형태로 정의할 수 있습니다.


products 쿼리를 요청하면 배열 형태의 Product를 기대할 수 있습니다.


GraphQLSchema를 사용하면 아래 같은 모습으로 정의할 수 있습니다.

const productType = new Graphql.GraphQLObjectType({
  name: "Product",
  fields:{
    id: { type: Graphql.GraphQLID },
    name: { type: new Graphql.GraphQLList(Graphql.GraphQLString) },
    price: { type: Graphql.GraphQLFloat },
    volume: { type: Graphql.GraphQLInt },
    overseas: { type: Graphql.GraphQLBoolean },
  }
});

const queryType = new Graphql.GraphQLObjectType({
  name: "Query",
  fields: {
    products: {
      type: new Graphql.GraphQLList(productType),
      resolve: () => {
        return [
          {id:"p001", name:["bb-8", "bb"], price:99.9, volume:126, overseas:false},
          {id:"p002", name:["r2d2", "r2"], price:99.9, volume:87, overseas:false}
        ];
      },
    }
  }
});


위 스키마에 의해 데이터는 아래 같은 모습으로 표현됩니다.

<[product]의 응답>




4. null 값 허용(Non-nullable)


buildSchema에서 객체 타입이나 스칼라 뒤에 !를 붙이면 field의 기대값으로 null을 허용하지 않겠다는 의미입니다.

var schema = buildSchema(`
  type Query {
    test: Test
  }

  type Test {
    case1: String!
    case2: [String!]
    case3: [String]!
    case4: [String!]!
  }
`);


위 스키마에 아래와 같은 값이 들어가면 에러가 발생하게 됩니다.

var root = {
  test: () => {
    return {
      case1: null,
      case2: [null],
      case3: null,
      case4: null // [null]
    }
  }
};


배열 자체가 null임을 허용하지 않는 경우, 배열 안의 값이 null인 것을 허용하지 않는 경우를 구분할 수 있어야합니다.


GraphQLShema에서는 GraphQLNonNull 클래스를 사용하여 non-nullable을 명시할 수 있습니다.

const productType = new Graphql.GraphQLObjectType({
  name: "Product",
  fields:{
    id: { type: new Graphql.GraphQLNonNull(Graphql.GraphQLID) },
    name: { type: Graphql.GraphQLString },
    price: { type: Graphql.GraphQLFloat },
    volume: { type: Graphql.GraphQLInt },
    overseas: { type: Graphql.GraphQLBoolean },
    time: { type: dateType },
    option: { type: OptionType },
  }
});


만약 데이터중에 null인 값이 있다면 아래같은 에러 메세지가 출력됩니다.

<non-nullable에 null이 들어갔을때>


5. 사용자 지정 스칼라 타입(Custom Scalar Types)


5가지의 기본 스칼라 타입 이외에 사용자가 스칼라 타입을 선언할 수 있습니다.

날짜를 표현하는 Date라는 타입을 선언해보겠습니다.


buildSchema의 경우에는 아래와 같습니다.

var schema = buildSchema(`
  type Query {
    products: [Product]
  }

  type Product {
    id: ID
    name: String
    price: Float
    volume: Int
    overseas: Boolean
    time: Date
  }

  scalar Date
`);

// 아래는 생략가능
Object.assign(schema._typeMap.Date, {
  name: "Date",
  description: "Date Type",
  serialize: (value) => {
    const date = new Date(value);
    if(date.toString()==="invalid Date"){ return null; }
    return date;
  }
});

var root = {
  products: () => {
    return [
      {id:"p001", name:"bb-8", price:99.9, volume:126, overseas:false, time:1540285936471},
      {id:"p002", name:"r2d2", price:99.9, volume:87, overseas:false, time:"12324-2-343"}
    ];
  },
};

Product 스키마에서 사용자가 임의로 지정한 스칼라 타입(Date)를 사용하는 모습을 볼 수 있습니다.


또한 schema._typeMap.SCALAR_NAME에 옵션을 주입하여 사용할 수 있습니다.


1. name : 임의 스칼라의 이름을 작성합니다.

2. description : 해당 스칼라의 설명을 작성합니다. 작성해두면 에러 메세지, graphiql 등에서 정보를 볼 수 있으므로 간략하게나마 명시해두는것이 좋습니다.

3. serialize : 이 함수에는 스키마에 해당하는 값이 인자로 들어오고 return을 통해 나오는 값이 GraphQL API에 표현됩니다.

이를 사용하여 DB에서 가져온 데이터를 parsing할 수 있습니다(scalar의 의도에 맞는지에 대한 유효성 검사, 데이터 format 변경 등)


옵션을 생략하면 제약조건 없는 임의의 값이 들어올 수 있게됩니다.

<time(Date Type)에 유효하지 않은 값이 들어갔을때>


serialize 함수를 통해 time:"12324-2-343" 값이 null로 출력되는 모습을 볼 수 있습니다.




GraphQLSchema로 스칼라 타입을 작성하는 방법은 아래와 같습니다.

const dateType = new Graphql.GraphQLScalarType({
  name: "Date",
  description: "Date Type",
  serialize: (value) => {
    const date = new Date(value);
    if(date.toString()==="invalid Date"){ return null; }
    return date;
  }
});

const personType = new Graphql.GraphQLObjectType({
  name: "Product",
  fields:{
    id: { type: Graphql.GraphQLID },
    name: { type: Graphql.GraphQLString },
    price: { type: Graphql.GraphQLFloat },
    volume: { type: Graphql.GraphQLInt },
    overseas: { type: Graphql.GraphQLBoolean },
    time: { type: dateType }
  }
});



6. 열거형(Enumeration Types)


열거형은 필드가 범주형 데이터를 가질 때 유용하게 쓸 수 있습니다.

var schema = buildSchema(`
  type Query {
    product: Product
  }

  type Product {
    id: ID
    name: String
    price: Float
    delivery_option: Option
  }

  enum Option {
    SHIP
    AIRPLANE
  }
`);

var root = {
  product: () => {
    return {id:"p001", name:"bb-8", price:99.9, delivery_option:"SHIP"};
  },
};

위 스키마에서 delivery_option에는 "SHIP" 또는 "AIRPLANE" 값 만이 유효하며, 이외의 값이 담겨질 경우 에러가 발생합니다.

인자(arguments)로 사용될 때도 범주 내 값만 사용할 수 있습니다.


GraphQLSchema에서는 아래와 같이 사용할 수 있습니다.

var OptionType = new Graphql.GraphQLEnumType({
  name: 'Option',
  values: {
    SHIP: { value: "SHIP" },
    AIRPLANE: { value: "AIRPLANE" }
  }
});

const personType = new Graphql.GraphQLObjectType({
  name: "Product",
  fields:{
    id: { type: Graphql.GraphQLID },
    name: { type: Graphql.GraphQLString },
    price: { type: Graphql.GraphQLFloat },
    delivery_option: { type: OptionType },
  }
});




7. 인수(Arguments)


객체 타입의 모든 필드는 인수(Argument)를 가질 수 있습니다.


buildShema에서는 아래처럼 인수를 정의할 수 있습니다.

var schema = buildSchema(`
  type Query {
    products(name: String = "bb-8"): [Product]
  }

# ... 생략
`);

var root = {
  products: (args) => {
    console.log(args);
    return [];
  },
};


위 예제는 name이라는 String 타입을 인수로 받고 그 기본값을 "bb-8"로 합니다.

쿼리를 요청할 때 기본적으로 인수를 넣을지 말지는 선택 사항이지만, 타입 뒤에 !를 붙이면 non-nullable하게 사용할 수 있습니다.


<인수를 사용하는 쿼리>

<products의 resolver에서 인수가 보여지는 모습>


GraphQLSchema에서는 아래처럼 인수를 사용할 수 있습니다.

const productType = ... 생략

const queryType = new Graphql.GraphQLObjectType({
  name: "Query",
  fields: {
    products: {
      type: new Graphql.GraphQLList(productType),
      args: {
        name: {type: Graphql.GraphQLString, defaultValue: "bb-8"},
      },
      resolve: (_, args) => {
        console.log(args);
        return [];
      },
    }
  }
});



8. 마치며


여기까지 스키마를 정의하는데 필요한 기본적인 내용들을 훑어 보았습니다.

공식 문서를 참조하여 작성한 글이라 의역한 부분이 있으니 애매한 내용이 있다면 댓글 남겨주세요.


이외에도 인터페이스(Interface), 유니온(Union types), 객체 입력(Input types), 별칭(Aliases), 프래그먼트(Fragments) 등 다양한 문법이 있습니다.

이에 대한 내용은 추후에 심화 문법이라는 주제로 다뤄보겠습니다.


추가적으로, buildSchema와 GraphQLSchema 사용법을 같이 작성하고 있지만 buildSchema는 GraphQL에서 utility성으로 제공하는 클래스라 모든 기능을 풍성하게 이용하려면 GraphQLSchema를 이용하는걸 권장드립니다.

(schema 객체를 잘 해부한다면 buildSchema에도 기능을 구현할수는 있습니다.)

'GraphQL' 카테고리의 다른 글

express-graphql에 mongodb 사용하기  (8) 2018.10.22
express-graphql 시작하기(Hello World Guide)  (4) 2018.10.14
Comments