본문 바로가기
JS

GraphQL로 영화 API 만들기 내용 정리

by 케찹이 2024. 3. 26.

 

원래는 프로젝트를 진행해야하지만 잠깐의 현타 + 조급함 덕분에 graphql강의를 들었다.

graphql에 대해서는 처음 배우는 것이고 단기간안에 강의 전체를 듣게 되어서 약간의 정리를 해보았다. 

(많은 기업의 자격요건, 우대요건에는 GraphQL이 포함되어있고 Relay도 사용도 있다.)

 

GraphQL탄생의 이유

REST API의 몇가지 단점을 해결하기 위해서 graphql을 쓴다고 한다. REST API는 매우 효율적이고 뛰어난 통신 방법이지만 두가지 단점을 가지고 있다. 

 

1. Overfetching: 우리가 REST API GET을 통해서 json형식의 데이터를 가져오면 필요 이상의 데이터를 가져올때가 있다. 이는 사용하지 않는 데이터임에도 불구하고 서버에서 가져옴으로 이러한 현상을 overfetching이라 하고 graphql에서는 이러한 단점을 해결할 수 있다.

2. Underfetching: overfetching은 모든 api에서 나타나는 현상이라고 한다면 underfetching은 몇몇 api에서 나타나는 현상이다. 이러한 현상은 프론트에서 원하는 정보를 가져오기 위해서 두개 이상의 api를 불러와야한다는 단점을 가져오고 있다. 이러한 단점을 underfetching이라고 하며 graphql을 통해서 해결할 수 있다. 

 

GraphQL 사용해보기

일단은 Apollo 서버를 설치해야한다. 정의를 한번 복붙해보면 아래와 같다.

"Apollo 서버는 Apollo 클라이언트를 포함한 모든 GraphQL 클라이언트와 호환되는 사양 준수(spec-compliant)의 오픈 소스 GraphQL 서버입니다. 모든 소스의 데이터를 사용할 수 있는 자체 문서화 가능한 production-ready GraphQL API를 구축하는 가장 좋은 방법입니다."

설치는 뭐다른 프로그램과 같이 npm -i apollo-server으로 하고 graphql도 같이 설치해준다.

 

import { ApolloServer, gql } from "apollo-server";

const typeDefs = gql`
  type User {
    id: ID!
    username: String!
    firstname: String!
    lastname: String!
  }
  type Tweet {
    id: ID
    text: String
    author: User
  }
  type Query {
    allTweets: [Tweet!]!
    tweet(id: ID!): Tweet
  }
  type Mutation {
    postTweet(text: String!, userId: ID!): Tweet!
    deleteTweet(id: ID!): Boolean!
  }
`;

const server = new ApolloServer({ typeDefs });

server.listen().then(({ url }) => {
  console.log(`Running on ${url}`);
});

 

위 코드를 보면 일단 ApolloServer인스턴스 생성시 typeDefs를 넣어줬는데, 해당 값을 살펴보면 gql안에 여러 타입을 만들어주었다.

여기서 중점으로 볼건 type Query와 type Mutation이다. (+이렇게 정의한 타입을 그래프 스키마라고 한다.)

type Query는 REST API의 GET방식 요청이다. allTweets: [Tweet!]! 이 부분은 /allTweets을 하면 리턴 타입으로 Tweet의 배열이 반환된다는 것. 그리고 type Mutation은 REST API의 POST요청이고 동시에 GET을 제외한 모든 요청이다. 

 

느낌표의 뜻은 null을 허용하지 않겠다는 의미이다. 기본적으로 graphql은 느낌표를 설정하지 않으면 nullable인 상태이다. 그렇기 때문에 느낌표 설정을 하지 않고 실제 사용할때 빈값을 넣어도 에러가 나오지 않는다.

 

Resolver

그 다음으로 url을 알았다면 resolver함수는 데이터베이스에 액세스한 다음에 데이터를 반환해주어야 한다. 

let tweets = [		// 원래는 DB에서 가져와야 하는 임시 데이터
  {
    id: "1",
    text: "first one!",
    userId: "2",
  },
];

......

const resolvers = {
  Query: {
    allTweets() {
      return tweets;
    },
    tweet(root, { id }) {		// 두번째 인자는 스키마에서 지정한 인자이고, 첫번째 인자는 root이다.
      return tweets.find((tweet) => tweet.id === id);
    },
    allUsers() {
      return users;
    },
  },
 }
 
 const server = new ApolloServer({typeDefs, resolvers});  // 여기에 resolvers추가!

 

resolver는 위에서 작성한 스키마와 같은 형태로 작성한다. 자세한 내용은 주석을 참고

Mutation resolver도 사실 Query resolver 형태가 똑같다고 봐도 된다.

 Mutation: {
    postTweet(_, { text, userId }) {
      const newTweet = {
        id: tweets.length + 1,
        text,
      };
      tweets.push(newTweet);
      return newTweet;
    },
    deleteTweet(_, { id }) {
      const tweet = tweets.find((tweet) => tweet.id === id);
      if (!tweet) return false;
      tweets = tweets.filter((tweet) => tweet.id !== id);
      return true;
    },
  },

 

기억해야할건 resolver의 형식은 이렇고 그안에 포함된 Javascript코드는 다른 언어로 표현 가능하고 외울건 아님. 

 

Resolver Argument

Resolver의 인자에는 root와 args가 포함된다고 하였다. 아래 예시를 보면서 그럼 root에는 어떤 값이 있는지 확인할 수 있다.

let users = [
  {
    id: "1",
    firstName: "nico",
    lastName: "last",
  },
  {
    id: "2",
    firstName: "Elon",
    lastName: "Musk",
  },
];

const typeDefs = gql`
  type User {
    id: ID!
    firstname: String!
    lastname: String!
    fullname: String!
  }
`;
const resolvers = {  
  Query: {
    allUsers() {
      return users;
    },
  }, 
 User: {
    fullName({ firstName, lastName }) {
      return `${firstName} ${lastName}`;
    },
  },
}

 

만약에 우리가 /allUsers를 부르게 된다면 users데이터들을 가져오게 된다. 이때 User의 스키마를 확인하면 fullname 타입이 존재하지만 위의 user데이터에는 fullname값이 없다. 이때 resolver를 한번 더 확인한다. 그럼 User resolver안에 fullname을 찾고 fullname을 호출해준다.

위 코드를 보면 알 수 있는 fullName의 root값에는 User를 담고 있다는 것을 알수 있다. 이러한 점은 fullName()과 allUsers()안에서 console.log()를 해보면 확실히 root값과 호출 순서를 알 수 있다. 

 

+) 사실 Resolver 함수에는 parent(root or source), args, context, info 의 네 가지 인수가 순서대로 전달된다.

https://www.apollographql.com/docs/apollo-server/data/resolvers/#resolver-arguments

 

위의 코드의 순서를 정리하자면 /allUsers를 부르면 먼저 resolver에서 allUsers()를 호출, 그러면서 user를 리턴한다. 이때 fullname값은 null이 아님에도 불구하고 user 데이터에 포함하지 않는다. 다시 resolver를 확인하고 User안에 fullname()을 확인하고 실행한다.

 

Documentation

GraphQL Studio에서 내 graphql API의 documentation을 확인할 수 있다. 확인해보면 내 api를 확인할 수 있는데 내 api에 설명을 작성하고 싶으면 내 코드에 """ """를 통해서 작성할 수 있다.

 """
 Tweet Object represents resources...
 """
 type Tweet {
    id: ID
    text: String
    author: User
  }

 

Migrate REST to GraphQL

이미 REST형식의 API를 GraphQL로 옮길 수 있다. 

모든 api의 키값을 받아와서, 하나씩 타입을 지정해주면 된다.... 노가다맞음

 

전체적으로 REST API + 타입스크립트인 느낌이다. 타입스크립트를 막 배우고 강의를 들어서 그런 느낌인가보다.

 

그냥 쓸때 모르겠으면 다시 강의봐야지 뭐...

https://nomadcoders.co/graphql-for-beginners/lectures/3715

 

All Courses – 노마드 코더 Nomad Coders

초급부터 고급까지! 니꼬쌤과 함께 풀스택으로 성장하세요!

nomadcoders.co

 

댓글