[GraphQL] Apollo sever를 이용하여 GraphQL API 만들기

조예진·2022년 6월 9일
2
post-custom-banner

📌SETUP

1. Apollo server

GraphQl API를 만들기 위해 Apollo server를 이용해 볼 것이다.

Apollo Server 오픈소스로, GraphQL을 이해하는 서버이다.

GraphQL은 specificaiton 그 자체이기 때문에 그걸 이해하는 서버를 이용해서 직접 구현해야한다.

Apollo Server의 장점은 Apollo server만 있어도 node.js server처럼 그 자체로 실행할 수 있다.

만약 express로 만들어진 REST API를 쓰고 있는데, 그 API를 GraphQL API로 바꿔주고 싶다면?

매우 간단하다. 미들웨어만 추가시켜주면 된다.

설치방법

자 이제 Apollo server를 설치해보자

먼저, node repository를 초기화할 것이다.

  1. node repository 초기화 명령어

    npm init -y

  2. Apollo server와 graphql 설치

    npm i apollo-server graphql

  1. nodemon 설치

    npm i nodemon -D

  2. server.js & gitignore 파일 생성

    touch server.js
    touch .gitignore

  3. package.json 변경

    script
    type: "module"
    ㄴ type을 추가해주면
    require이 아니라 import로 쓸 수 있다.

예시 :

#type 추가했을 시
import { ApolloServer, gql } from "apollo-server";

#type 추가 안했을 시
const { ApolloServer, gql } = require("apollo-server")

  1. server.js에 Apollo server추가하기
import { ApolloServer, gql } from "apollo-server";
  1. 터미널 실행

    npm run dev

자, 여기까지 이제 graphQL을 만들기 위한 SERVER세팅이다.

GraphQL 스키마 정의


이제 본격적으로 GraphQL에 대해 알아보자!

📍핵심: 서버를 올리기 전 graphQL 스키마 정의하기

server.js파일에 아래 코드 추가

const server = new ApolloServer({})
server.listen().then(url => {
    console.log(url)
})

const server = new ApolloServer({})
이와 같이 server에 아무런 스키마를 정의해주지 않는다면
아래와 같은 서버에러가 날 것이다.

Apollo server는 스키마, 타입 정의가 필요하다

스키마 정의란?

return하려는 data 가 무엇인지
사용자가 보내는 data가 무엇일지
사용자가 쓸 수 있는 query가 무엇인지

위와 같이 데이터 field, type, request는 Get, POST, PUT, DELETE 등 중 무엇인지에 대해 정의해주는 것을 말한다.

(MongoDB & mongoose를 써봤다면,
mongoose schema 모델에 대한 데이터 모든 타입을 정의해본 적이 있을 것이다. 조금 다르지만 비슷한 맥락으로 볼 수 있다.)

스키마 정의 예시

const typeDefs = gql`
  
  type User {
    id :ID
    username: String
  }

  type Tweet {
    id : ID
    text:String
    author: User
  }

  type Query {
    allTweets: [Tweet] 
    tweet(id: ID): Tweet  
    ping : String
  }

  type Mutation {
	postTweet(text:String, userId: ID!):Tweet!
    deleteTweet(id: ID) : Boolean!
}

SDL(Schema Definition Language)

graphQL을 쓰기 위해 schema definition language를 통해 데이터를 정의할 수 있다.

1. Get 요청: Query type

📍핵심: 요청 메소드 중 GET에 해당하는 type

REST API 요청과 비교해보자

GET /text
GET /hello
GET /animals

text, hello를 요청하게 데이터를 정의해주고 싶다면,
graphQL은 아래와 같이 정의할 수 있다.

const typeDefs = gql`
        type Query {
            text:String
            hello:String
            animals: [animal]
        }`;

위 스키마 정의를 보면,
text, hello와 다르게 생긴 animals를 볼 수 있다.

Query type은 두가지 타입으로 데이터를 정의할 수 있다.

scalar type vs non-scalar type

scalar type은
string, int 와 같은 타입을 말한다.

non-scalar type은
직접 정의한 타입을 말한다

const typeDefs = gql`
        type Query {
            text:String
            hello:String
            animals: [animal]
        }`;

animals의 type이 animal인 것은 직접 정의한 타입으로 return해주겠다는 의미이다.

graphQL은 단순히 int, string, boolean과 같이 내장된 타입의 데이터만 반환하지 않고, 다양한 타입을 직접 정의하여 데이터를 반환할 수 있어 유연하다.

그렇다면, non-scalar type은 직접 정의한 타입이라고 했는데, 어떻게 정의할까?

const typeDefs = gql`
       type animal {
       		id: String
            name: String
       }
       
       type Query {
            text:String
            hello:String
            animals: [animal]
        }`;

type animal과 같이 데이터를 내부적으로 정의하여
Query type의 animals를 요청하면 [animal] 즉, animal 리스트를 조회할 수 있도록 타입을 지정한 것이다.

여기서 의문을 가질 수 있다.
그러면 특정 animal을 조회하고 싶으면 어떻게 할까?

Rest API로 예를 들자면,
아래는 id 1번인 동물을 가져오는 url이다

GET /animal/:id 
즉, GET/animal/1

GraphQL은

const typeDefs = gql`
       type animal {
       		id: String
            name: String
       }
       
       type Query {
       		animal(id:ID):animal
            animals: [animal]
        }`;

animal(id:ID):animal 처럼 id를 인자로 받고, 해당 인자의 타입을 ID로 지정해준다.
조회(return)할 타입 :animal로 정의해준다.

2. post, put, delete 요청: Mutation type

📍핵심: 요청 메소드 중 post, put, delete 해당하는 type

Rest API는
등록, 수정, 삭제 시 아래와 같이 각각 다른 method를 가진다.

POST /add/animal
PUT /mod/animal/:id
DELETE /del/animal/:id

하지만, GraphQL은 데이터를 변경하는 요청 관련은
전부 Mutation type에 정의해준다.

const typeDefs = gql`
  type Mutation {
    postAnimal(name:String):Animal!
    putAnimal(id:ID):Animal! 
    deleteAnimal(id: ID) : Boolean!
  }`;

간략하게 postAnimal로 설명해보자면,
postAnimal(name:String):Animal!

(name:String): string 타입의 name을 인자로 받겠다.
즉, 사용자가 요청 시 추가할 동물의 이름 필요

:Animals!는 데이터 변경 후 return할 데이터 타입을 정의해준 것이다.
(string, int, boolean과 같이 내장된 scalar type이 아닌 직접 정의한 non-scalar 타입으로 반환하겠다는 뜻이다.)

! << 이 부분은 Null타입을 허용하지 않겠다는 문법인데, 아래에서 자세히 다룰 예정이다.

GraphQL Resolvers


위에 정의한 데이터들을 어떻게 데이터베이스에 있는 데이터와 연결해서 응답해줄까?

graphQL은 resolvers라는 함수를 통해 데이터를 바인딩 시키고,
필요하다면 해당 부분에서 로직을 실행하고,
최종적으로 사용자게에 요청받은 데이터를 return해준다.

즉, resolvers 함수를 통해 데이터를 보낼 수 있다.

1. Query resolvers

일단 임시 데이터베이스를 만들어서 해당 데이터를 조회한다.

const animals = [
  {
    id: "1",
    text: "dog",
  },
  {
    id: "2",
    text: "cat"
  },
  {
    id: "3",
    text: "rabbit"
  }
]

다음과 같이 스키마를 정의했고,

const typeDefs = gql`
  
  type Animal {
    id : ID
    text:String
  }

  type Query {
    animals: [Animal] 
    animal(id: ID): Animal  
  }
`;

resolvers 객체를 만들었다.

const resolvers = {
  Query: {
    animals() {
      return animals 
    }
  }
}

resolvers도 type Query의 데이터와 관련되었다면,
위와 같이 Query 안에서 로직을 작성해주어야한다.

animals()처럼 어떤 요청에 대한 응답인지 알 수 있도록
우리가 type Query에 지정해준 필드명과 일치하게 필수로 작성해주어야한다.

return animals로 가짜로 만든 데이터를 조회할 수 있도록 resolvers 함수를 만들었다.

이제 맨 위에서 만들었던 apolloserver에 스키마 정의와, resolvers를 인자로 넣어서 server에서 직접 확인해 볼 수 있다.

const server = new ApolloServer({ typeDefs, resolvers});
server.listen().then(({ url }) => {
  console.log(`Running on ${url}`);
})

아래와 같이 해당 쿼리를 요청했을 경우 오른쪽 response에서 정상적으로 animals 리스트가 출력되는 것을 확인할 수 있다.

2. Mutate resolvers

type Mutation {
postAnimal(name:String):Animal!
putAnimal(id:ID, name:String):Animal!
deleteAnimal(id: ID) : Boolean!
}

const resolvers = {
  Query: {
    animals() {
      return animals 
    }
  },
Mutation : {
    postAnimal(_, {name}) {
      animals.push({id: (animals.length), name})
      return animals
    }
  }
}

스키마에서 정의한 putAnimal과 일치하는 mutaion resolvers를 만들고 데이터베이스에 해당 데이터를 추가하고
추가한 데이터를 반환해주는 로직을 생성해주었다.

필드는 인자를 두개 받을 수 있는데
putAnimal(root, args)
한개는 루트이고, 한개는 사용자가 넘겨준 인자들이다.

사용자가 보낸 데이터는 무조건 두번째 인자로 오기 때문에 첫번째 인자를 안쓰더라도 명시는 해주어야한다.

추가하자!

  • query에서 mutation을 할경우 어떻게 될까
profile
블로그 이전 중 -> https://devjooj.tistory.com/
post-custom-banner

0개의 댓글