본문 바로가기

백엔드/Prisma + GraphQL

[인스타그램 클론코딩] toggleFollow

※ prisma의 upsert를 사용하면 더 간단히 구현할 수 있습니다.

GraphQL을 아직 잘 모르겠다면?

나의 첫번째 GraphQL서버 만들기

 

팔로우 버튼은 특정 사용자와의 팔로우 상태를 반전시키는 버튼입니다.

그리고 toggleFollow는 클라이언트에서 팔로우 버튼을 눌렀을 때 백앤드에서 실행되는 Resolver입니다. 

클라이언트에서 피드를 요청하면 백앤드는 요청자와 팔로우 상태인 사용자의 포스트만 백앤드에 요청합니다. 위의 동영상에서 팔로우가 해제됨에 따라 용 그림의 포스트가 사라지는 것을 확인할 수 있습니다.

 

toggleFollow.graphql

팔로우되거나 언팔로우될 다른 사용자의 ID를 필수로 받아서 해당 사용자의 정보를 반환하는 Resolver입니다. Boolean 타입을 반환하도록 해도 상관없습니다. 저는 ApolloClient의 캐시에 영향을 미치는지 테스트하기 위해서 User 타입을 반환하도록 설정했습니다.

type Mutation {
    toggleFollow(id: Int!): User
}

Mutation은 데이터베이스를 변형시킨다는 것을 의미합니다.

 

클라이언트 (리액트 네이티브, 리액트 앱)

사용자가 다른 사용자의 프로필에서 팔로우 버튼을 터치하면, 다음과 같이 해당 사용자의 ID를 매개값으로 입력한 toggleFollow 요청식을 백앤드에 전송합니다.

특정 타입을 반환하는 Resolver에 아무런 필드도 요청하지 않으면 오류가 발생하기도 하고, 오류 없이 완료됐는지 확인하기 위해 팔로우되거나 언팔로우될 다른 사용자의 id 필드만이라도 요청해 줍니다. 

import { gql } from "apollo-boost";
import { useMutation } from "react-apollo-hooks";

// GraphQL API 요청식
const TOGGLE_FOLLOW = gql`
  mutation toggleFollow($id: Int!) {
    toggleFollow(id: $id) {
      id
    }
  }
`;

// GraphQL API 요청 함수 생성(아직 실행x)
const [toggleFollowMutation] = useMutation(TOGGLE_FOLLOW, {
    variables: {
        id // 팔로우 또는 언팔로우할 사용자의 ID
    },
    // 팔로우 사용자가 변경됨에 따라 FEED_QUERY(홈), ME(프로필) 쿼리 갱신
    // GraphQL API 요청식은 생략
    refetchQueries: () => [{ query: FEED_QUERY }, { query: ME }]
});

// 사용자가 팔로우 버튼을 눌렀을 때, 핸들러에서 실행
await toggleFollowMutation();

toggleFollow.js

백앤드 서버는 toggleFollow 요청을 받으면 ApolloServer 객체 생성시 입력했던 schema.resolvers에서 toggleFollow 함수(Resolver)를 찾아서 실행시킵니다.

참조1: GraphQL 데이터 모델링 도구 (@graphql-tools, nexus-prisma)

참조2: 서버리스 GraphQL 백앤드 구축

const _default = {
    Mutation: {
        toggleFollow: async(_, args, { prisma, me, isValid }) => {
            if(isValid !== true) throw Error("NotAuthenticated");

            const { id } = args; // follower's id

            try {
                const alreadyFollowing = (await (await prisma.user.findOne({
                    where: {id: me.id},
                    include: { following: true }
                }))
                .following
                .map(user => user.id))
                .includes(id)

                if(!alreadyFollowing) { // 팔로잉
                    await prisma.user.update({
                        where: {id: me.id},
                        data: {
                            following: {
                                connect: {
                                    id
                                }
                            }
                        },
                        include: { following: true }
                    })
                } else { // 언팔로잉
                    user = await prisma.user.update({
                        where: {id: me.id},
                        data: {
                            following: {
                                disconnect: {
                                    id
                                }
                            }
                        },
                        include: { following: true }
                    })
                }
                return user;
            } catch (e) {
                console.log(e);
            }
        }
    }
}

exports.default = _default;

하나씩 알아볼까요?

먼저 인증된 사용자인지 확인합니다. 인증되지 않은 사용자라면 코드 첫 줄에서 오류를 반환합니다.

참조1: Cognito ID 토큰 복호화

참조2: [인스타그램 클론코딩] 사용자 인증 & GraphQL Context 활용

if(isValid !== true) throw Error("NotAuthenticated");

 

클라이언트에서 toggleFollow를 요청할 때 입력한 매개변수를 toggleFollow Resolver의 두 번째 매개변수에서 추출할 수 있습니다. 

const { id } = args; // follower's id

 

클라이언트에서 전송받은 다른 사용자의 ID요청자의 following에 포함되어 있는지 확인하는 코드입니다.

체이닝 코드는 보기 어려울 것 같아 밑에  3개의 코드로 분리했습니다.

const alreadyFollowing = (await (await prisma.user.findOne({
    where: {id: me.id},
    include: { following: true }
}))
.following
.map(user => user.id))
.includes(id);

// 다음과 동일
// Prisma 클라이언트를 사용하여 데이터베이스에서 요청자의 following을 구함.
const user = await prisma.user.findOne({
     where: {id: me.id},
     include: { following: true }
});

// 요청자의 following에 포함된 User 레코드의 id만 추출해서 배열로 만듦.
const Following = await user.following.map(user => user.id);

// Follow 배열에서 팔로우하길 원하는 다른 사용자의 ID가 포함되어 있는지 확인
const alreadyFollowing = Following.includes(id);

 

alreadyFollowing이 false이면, Prisma를 사용하여 요청자의 following에 팔로우하길 원하는 다른 사용자를 추가(update + connect)시키고, true이면 삭제(update + disconnect)시킵니다.

참조: [인스타그램 클론코딩] Prisma2 데이터 모델링

// 팔로잉
await prisma.user.update({
    where: {id: me.id},
    data: {
        following: {
            connect: {
                id
            }
        }
    },
    include: { following: true }
});

// 언팔로잉
await prisma.user.update({
    where: {id: me.id},
    data: {
        following: {
            disconnect: {
                id
            }
        }
    },
    include: { following: true }
});

update는 기존 데이터를 변경하는 메소드이고, connect, disconnect 속성으로 _UserFollows 테이블에 두 사용자의 추가 또는 삭제 여부를 결정할 수 있습니다.

참조1: Prisma2 데이터 모델링3

참조2: Prisma2 CLI를 사용하여 데이터베이스 테이블 생성