thumnail.png

이전 포스트 : React와 GraphQL로 채팅 구현하기 - 서버 환경 설정

이제 본격적으로 채팅서비스 API를 만들어 보자!

GraphQL에서 쿼리를 작성하기 위해서는 일단 쿼리에 대한 타입이 필요하다. 우리가 만들 채팅 서비스에서 필요한 채팅에 관련된 타입을 정의해보자

타입 정의

type Chat{
	id: Int!
	writer: String!
	content: String!
}

src/api/chat/shared/Chat.graphqlChat이라는 타입을 만들었다. 앞으로 우리는 이 타입을 사용해 채팅을 조회하고 구독하는데 사용할 것이다. 구조는 간단하게 id, 그리고 작성자와 내용으로 구성했다.

채팅 조회 구현

채팅을 조회하려면 채팅이 저장되었는 공간이 필요하다. 실제 서비스라면 db를 사용하거나 다른 저장소를 사용했겠지만 이번 채팅서비스는 간단하게 구현하는 것이 목표이기 때문에 메모리에 배열을 사용해 저장하려고 한다.

export default [
  {
    id: 1,
    writer: 'chanyeong',
    content: 'first chat!',
  },
];

src/api/chat/shared/chatList.ts에 다음과 같이 초기 채팅 데이터를 가지고 있는 배열을 만들어준다. 앞으로 모든 채팅은 이 배열에 저장할 것이다.

자! 이제 채팅 조회에 대한 API를 작성할 것이다. API를 만들기 위해서는 기본적으로 해당 API의 타입이 정의된 graphql파일과 행동이 정의된 resovlers함수를 구현해야 한다.

type Query {
  getChatList: [Chat!]!
}

src/api/chat/getChatList/getChatList.graphql에 먼저 타입부터 정의해보자. 데이터를 조회하는 API이기 때문에 Query 타입으로 선언 후 쿼리에 대한 이름을 작성해 줬다. 옆에 반환 타입을 지정해 줬는데 좀 전에 shared파일에 생성한 Chat의 배열타입을 반환 타입으로 지정해줬다.

저 타입을 다른 파일에 작성했지만 사용할 수 있는 이유는 이전 포스트에서 모든 타입을 병합한 뒤 서버에 스키마로 넣어줬기 때문에 다른 파일에 작성한 타입도 사용할 수 있다.

npm run types

# ✔ Parse configuration
# ✔ Generate outputs

이제 우리가 작성한 graphql파일로 타입스크립트 타입을 생성해줘야 한다. 이전 포스트에서 설정한 대로 다음 명령어를 통해 타입을 생성하자! 정상적으로 타입이 생성 되었다면 위와 같은 메시지가 표시된다. (서버는 실행중이여야 한다.)

import { Resolvers } from '../../../types';
import chatList from '../shared/chatList';

const resolvers: Resolvers = {
  Query: {
    getChatList: () => chatList,
  },
};

export default resolvers;

src/api/chat/getChatList/getChatList.resolvers.tsresolvers함수를 작성해보자! 파일 이름에는 무조건 .resolvers.를 넣어줘야 나중에 graphql서버에서 인식할 수 있다. (이전 포스트에서 병합 설정)

다음과 같이 resolvers함수에는 쿼리의 타입(Query, Mutation, Subscription)과 그 안에 쿼리의 이름을 입력 후 API 함수를 작성하면 된다. 채팅을 조회하는 API이기 때문에 요청 시 아까 만든 chatList 배열을 그대로 반환시켜 줬다.

0.png

다음과 같이 playground에서 쿼리를 실행시켜 봤는데 정상적으로 채팅을 반환하는 것을 확인할 수 있다.

채팅 작성 구현

채팅 조회도 구현 했으니 이제 채팅을 작성하는 API도 구현해보자! 우선 아까와 같이 쿼리의 타입과 resolvers함수부터 정의해야 한다.

type AddChatResonse {
  result: Boolean!
  error: String
}

type Mutation {
  addChat(writer: String!, content: String!): AddChatResonse!
}

src/api/chat/addChat/addChat.graphql에 다음과 같이 타입을 작성해준다. 좀 전의 getChatList의 타입과는 조금 다르다. 일단 쿼리의 반환 타입을 새로 생성해 줬다. Mutation은 데이터 조작이 발생하는 쿼리이기 때문에 성공과 실패를 의미하는 result와 에러가 발생했을 경우 에러 메시지를 담을 error를 타입으로 명시해줬다.

그리고 Mutation 타입 안에 addChat이라는 쿼리를 정의해줬다. 매개변수로는 작성자와 내용을 받고 좀 전에 만든 AddChatResonse라는 반환 타입을 갖도록 했다.

이제 다시 npm run types를 통해 타입을 생성하고 resolvers함수를 정의해야 한다.

import { Resolvers } from '../../../types';
import chatList from '../shared/chatList';

const resolvers: Resolvers = {
  Mutation: {
    addChat: (_, { writer, content }) => {
      const newChat = { id: chatList.length + 1, writer, content };
      chatList.push(newChat);
      return { result: true };
    },
  },
};

export default resolvers;

src/api/chat/addChat/addChat.resolvers.ts에 다음과 같이 resolvers함수를 작성한다. addChat 메서드의 두 번째 매개변수로 입력받은 작성자와 내용을 받아 새로운 채팅을 만들어 기존의 chatList배열에 추가시켜주고 성공했다는 result를 반환했다.

1.png

작성한 API를 확인해보니 정상적으로 채팅이 추가되는 것을 볼 수 있다.

2.png

getChatList쿼리를 다시 실행시켜 정상적으로 채팅이 들어갔는지도 확인해 보았다. 방금 추가한 채팅이 정상적으로 잘 들어간 것을 확인할 수 있다. 😁

채팅 구독 구현

채팅에서 가장 중요한 기능은 실시간으로 새로 작성된 채팅을 불러오는 것이다. 그렇기 때문에 채팅을 구독하는 기능을 구현해야 한다. 사용자는 채팅목록에 채팅이 추가되는 것을 구독하고 있다가 상대방이 채팅을 작성해 채팅목록에 추가하면 실시간으로 사용자의 화면으로 새롭게 추가된 채팅을 가져와야 한다.

type Subscription {
  subChat: Chat
}

src/api/chat/subChat/subChat.graphql파일에 다음과 같은 타입을 작성한다. subChat이라는 쿼리로 구독을 하면 새로운 데이터를 Chat타입으로 보내주는 코드이다.

타입을 작성했으니 역시 npm run types로 타입을 생성한다.

import { Resolvers } from '../../../types';

export const NEW_CHAT = 'NEW_CHAT';

const resolvers: Resolvers = {
  Subscription: {
    subChat: {
      subscribe: (_, __, { pubsub }) => {
        return pubsub.asyncIterator(NEW_CHAT);
      },
    },
  },
};

export default resolvers;

src/api/chat/subChat/subChat.resolvers.ts에 다음과 같이 resolvers함수를 작성한다. Subscription은 함수를 작성할 때 subChat이라는 쿼리 이름안에 다시한번 객체로 감싸서 subscribe라는 프로퍼티 안에 함수를 작성해야 한다.

세 번째 인자로 context객체 안의 pubsub을 받았는데 지난 포스트에서 Apollo Server를 설정할 때 context객체 안에 pubsub을 생성 후 넣어줬기 때문에 사용할 수 있다.

pubsubsubscription기능을 사용할 때 구독, 발행 시 사용할 수 있는 객체이다. 현재 subChat에서는 NEW_CHAT이라는 이름으로 데이터를 구독하기위해 사용되었다.

NEW_CHAT은 데이터를 발행할 때도 사용해야 하기 때문에 export 키워드를 사용해 다른 코드에서도 접근할 수 있어야 한다.

import { Resolvers } from '../../../types';
import chatList from '../shared/chatList';
// NEW_CHAT 불러오기
import { NEW_CHAT } from '../subChat/subChat.resolvers';

const resolvers: Resolvers = {
  Mutation: {
    addChat: (_, { writer, content }, { pubsub }) => {
      const newChat = { id: chatList.length + 1, writer, content };
      chatList.push(newChat);
			// NEW_CHAT이름으로 데이터 발행
      pubsub.publish(NEW_CHAT, { subChat: newChat });
      return { result: true };
    },
  },
};

export default resolvers;

여기까지 했으면 아직 구독자는 채팅이 추가되었는지 추가되지 않았는지 확인할 수 없다. 그러니 채팅 추가 API에 채팅을 추가하고 NEW_CHAT이라는 이름으로 데이터를 발행해 줘야 한다.

src/api/chat/addChat/addChat.resolvers.ts에 다음과 같이 NEW_CHAT을 불러오고 데이터를 발행하는 코드를 추가한다.

3.png

그리고 playground에서 subChat쿼리를 실행 후 addChat쿼리로 채팅을 추가해 보니 정상적으로 구독된 채팅 데이터가 넘어오는 것을 확인할 수 있었다.

이것으로 채팅 서비스의 Apollo Server 개발도 끝나게 되었다.

다음 포스트는 GraphQL을 사용한 React Client 환경 설정에 대한 내용을 다룰 예정이다.!