GraphQL - Resolver / Schema

박정호·2023년 1월 24일
0

API

목록 보기
6/6
post-thumbnail

🚀 Start

GraphQL을 사용하기 위해서는 반드시 알아야할 개념이 있다. 바로 Resolvers, Schema 이다.



⭐️ Schema

GraphQL의 API를 설계하기 전에 항상 사용할 스키마를 먼저 정의해햔다.

왜냐하면 GraphQL 쿼리의 형태는 리턴되는 값과 거의 일치한다. 어떤 필드를 선택할지 어떤 종류의 객체를 반환할지, 하위 객체에서 필요한 필드는 무엇인지 알기 위해서는 스키마의 존재는 필수다.

따라서, 스키마에는 타입 정의를 모아둔다.

참고하자 👉 schema & type 공식문서

Request Response

요청값과 응답값이 형태가 유사한 것을 알 수 있고, 이것이 원하는 정보만 요청 가능한 graphQL의 장점이다.

//Request
{
  hero {
    name
    appearsIn
  }
}
//Response
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ]
    }
  }
}

Scema

type Character {
  name: String!
  appearsIn: [Episode]!
}


Type

Type은 GraphQL 스키마의 핵심 단위이다. GraphQL에서 타입은 커스텀 객체이며 다음과 같은 타입 종류가 있다.


Example

1️⃣ Character는 GraphQL의 객체 타입이다.

스키마는 대부분 객체 타입이라고 보면 된다. 이런 타입은 애플리케이션의 데이터를 상징한다. 그리고 곧 팀에서 도메인 객체에 대해 이야기할 때 사용할 공통의 언어(Ubiquitous Language) 를 정의하는 것과 같다.

따라서 이를 보고 애플리케이션의 핵심 기능을 알 수 있도록 짓는 것이 중요하다.

type Character { // 1️⃣ 번
  id: ID! 
  name: String!
  appearsIn: [Episode]!
}

2️⃣ ID!는 GraphQL의 스칼라 타입

고유 식별자 값이 반환되어야 하는 곳이다. name, appearsInCharacter 타입의 필드(field) 이다.

필드는 각 객체의 데이터와 관련이 있으며 각각의 필드는 특정 종류의 데이터를 반환한다. 이때 문자열, 커스텀 객체 타입, 여러 타입을 리스트로 묶어 반환하기도 한다.

type Character { 
  id: ID! // 2️⃣  번
  name: String!
  appearsIn: [Episode]!
}

3️⃣ String은 GraphQL에 내장된 스칼라 타입(기본 타입) 중 하나

String!은 필드가 non-nullable임을 의미한다.

다시 말해 필수값을 정의했다고 보며, 이 값은 쿼리할 때 항상 값을 반환한다는 것을 의미한다. 타입 언어에서는 ! 느낌표로 나타낸다.

  • non-nullable이란 null 값을 허용하지 않는다는 것을 의미
  • !값이 없다면 null 값을 허용하는 옵셔널(nullable) 값.
type Character { 
  id: ID! 
  name: String! // 3️⃣  번
  appearsIn: [Episode]!
}

4️⃣ [Episode]!는 Episode 객체의 배열(Array)

!이기 때문에 appearsIn 필드를 쿼리할 때 항상 0개 이상의 아이템을 가진 배열을 기대할 수 있다.

type Character { 
  id: ID! 
  name: String! 
  appearsIn: [Episode]! // 4️⃣  번
}


⭐️ Resolvers

graphQL에서는 데이터를 가져오는 구체적인 과정을 직접 구현해야한다. gql 쿼리문 파싱은 대부분의 gql 라이브러리에서 처리를 하지만, gql에서 데이터를 가져오는 구체적인 과정은 resolver가 담당하고, 이를 직접 구현해야 한다.

쉽게 말해 querymutation을 그냥 사용할 수 없으며, 어딘가에 정의되어야 하며 바로 rsolver에 정의하는 것이다. 따라서, 모든 유형의 모든 필드는 resolver라는 함수에 의해 지원된다.

직접 구현해야한다는 부담은 있지만, 이를 통해서 데이터 source 종류에 상관없이 구현이 가능하다.

💡 공식문서
: 리졸버는 스키마의 단일 필드에 대한 데이터 채우기를 담당하는 기능입니다. 백엔드 데이터베이스 또는 타사 API에서 데이터를 가져오는 것과 같이 정의한 방식으로 해당 데이터를 채울 수 있습니다.(참고)


Resolver 함수의 선택적 4개 인수

fieldName: (parent, args, context, info) => data;

👉 Parent
: 이것은 이 필드의 부모에 대한 해석기의 반환 값
(상위 필드에 대한 해석기는 항상 해당 필드의 자식에 대한 해석기보다 먼저 실행됨).

👉 args 
: 이 객체는 이 필드에 제공된 모든 GraphQL 인수를 포함

👉 context
:  이 개체는 특정 작업을 실행하는 모든 해결 프로그램에서 공유.
이를 사용하여 인증 정보 및 데이터 소스에 대한 액세스와 같은 작업별 상태를 공유.

👉 info
: info 이것은 작업의 실행 상태에 대한 정보를 포함.(고급 사례에서만 사용됨).

Example

const { ApolloServer, gql } = require("apollo-server");

// Inmemory data
const users = [
    {
        id: "1",
        name: "Elizabeth Bennet",
    },
    {
        id: "2",
        name: "Fitzwilliam Darcy",
    },
];

// GraphQL schema definition
const typeDefs = gql`
    type User {
        id: ID!
        name: String
    }

    type Query {
        user(id: ID!): User
    }
`;

// ⭐️ resolver
const resolvers = {
    Query: {
        user(parent, args, context, info) {
            return users.find((user) => user.id === args.id);
        },
    },
};

const server = new ApolloServer({ typeDefs, resolvers });

server.listen({ port: 9000 }).then(({ url }) => {
    console.log(`🚀 Server ready at ${url}`);
});

참고하자 👉 Resolvers 공식문서



🖥 In Project

👉 Client

// graphql/message.ts
import gql from "graphql-tag";

export const GET_MESSAGES = gql`
  query GET_MESSAGES {
    messages {
      id
      text
      userId
      createdAt
    }
  }
`;

export const GET_MESSAGE = gql`
  query GET_MESSAGE($id: ID!) {
    message(id: $id) {
      id
      text
      userId
      createdAt
    }
  }
`;

export const CREATE_MESSAGE = gql`
  mutation CREATE_MESSAGE($text: String!, $userId: ID!) {
    createMessage(text: $text, userId: $userId) {
      id
      text
      userId
      createdAt
    }
  }
`;

export const UPDATE_MESSAGE = gql`
  mutation UPDATE_MESSAGE($id: ID!, $text: String!, $userId: ID!) {
    updateMessage(id: $id, text: $text, userId: $userId) {
      id
      text
      userId
      createdAt
    }
  }
`;

export const DELETE_MESSAGE = gql`
  mutation DELETE_MESSAGE($id: ID!) {
    deleteMessage(id: $id)
  }
`;


👉 Server

Schema

// schema/message.ts
import { gql } from "apollo-server-express";

const messageSchema = gql`
  type Message {
    id: ID!
    text: String!
    userId: String!
    createdAt: Float #13자리 숫자
  }
  extend type Query {
    messages(cursor: ID): [Message!] # getMessages
    message(id: ID!): Message! # getMessage
  }
  extend type Mutation {
    createMessage(text: String!, userId: ID!): Message!
    updateMessage(id: ID!, text: String!, userId: ID!): Message!
    deleteMessage(id: ID!): ID!
  }
`;

export default messageSchema;

Resolver

// resolvers/messages.ts
const messageResolver: Resolver = {
  Query: {
      messages: async (parent, { cursor = "" }) => {
          ...
      },
      message: async (parent, { id = "" }) => {
         ...
      },
  },
  Mutation: {
    createMessage: async (parent, { text, userId }) => {
     	...
    },
    updateMessage: async (parent, { id, text, userId }) => {
   	    ...
    },
    deleteMessage: async (parent, { id }) => {
     	...
    },
  },
};


🔗 Reference
👉 Resolvers 알아보기
👉 GraphQL 개념잡기
👉 GraphQL 스키마 정의

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글