본문 바로가기

백엔드/Prisma + GraphQL

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

GraphQL을 아직 잘 모르겠다면?

나의 첫번째 GraphQL서버 만들기

 

좋아요 버튼은 각각의 포스트에 포함된 하트(♥) 모양의 버튼입니다. 

그리고 toggleLike는 포스트의 좋아요 버튼을 눌렀을 때, 백앤드에서 실행되는 Resolver입니다.

 

toggleLike.graphql

요청자의 Like 레코드가 연결(등록)되거나 해제될 포스트의 ID를 필수로 받아서 해당 포스트의 정보를 반환하는 Resolver입니다. Boolean 타입을 반환하도록 해도 상관없습니다. 저는 ApolloClient의 캐시에 영향을 미치는지 테스트하기 위해서 Post 타입을 반환하도록 설정했습니다.

type Mutation {
    toggleLike(postId: Int!): Post!
}

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

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

사용자가 어떤 포스트의 좋아요 버튼(♥)을 터치하면, 해당 포스트의 ID를 매개값으로 입력한 toggleLike 요청식을 백앤드에 전송합니다.

특정 타입을 반환하는 Resolver에 아무런 필드도 요청하지 않으면 오류가 발생하기도 하고, 오류 없이 완료됐는지 확인하기 위해 해당 포스트 id 필드만이라도 요청해 줍니다.

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

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

// GraphQL API 요청 함수 생성(아직 실행x)
const [toggleLikeMutation] = useMutation(TOGGLE_LIKE, {
    variables: {
        postId: id // 좋아요 버튼이 포함된 포스트 ID
    }
});

// 사용자가 좋아요 버튼을 눌렀을 때, 핸들러에서 실행
await toggleLikeMutation();

toggleLike.js

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

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

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

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

            const  { postId } = args;

            const filterOptions = {
                where: {
                    AND:[
                        {
                            userId: me.id
                        },
                        {
                            postId
                        }
                    ]
                }
            };

            try {
                const existingLike = await prisma.like.findMany(filterOptions);
                if(existingLike.length > 0) {
                    const like = await prisma.like.deleteMany(filterOptions);
                } else {
                    const like = await prisma.like.create({
                        data: {
                            user: {
                                connect: {
                                    id: me.id
                                }
                            },
                            post: {
                                connect: {
                                    id: postId
                                }
                            }
                        }
                    });
                }
            } catch(error) {
                console.log(error);
            } finally {
                return prisma.post.findOne({
                    where: {
                        id: postId
                    }
                })
            }
        }
    }
}

exports.default = _default;

==추가==

최근에는 upsert를 사용합니다.

하나씩 알아볼까요?

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

참조1: Cognito ID 토큰 복호화

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

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

 

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

const  { postId } = args;

 

동일한 조건이 중복 사용되는 경우, 다음과 같이 조건 객체를 만들어 사용할 수 있습니다.

// userId가 요청자의 ID이고, postId가 매개값으로 입력된 포스트의 ID인 조건 객체
const filterOptions = {
    where: {
        AND:[
            {
                userId: me.id
            },
            {
                postId
            }
        ]
    }
};

// 요청자가 현재 포스트에 이미 좋아요 버튼을 클릭했는지 확인
// userId가 요청자의 ID이고, postId가 매개값으로 입력된 포스트의 ID인 Like 레코드 검색
const existingLike = await prisma.like.findMany(filterOptions);

 

existingLike가 false라면 Prisma 클라이언트를 사용해서 데이터베이스에 요청자와 매개값으로 받은 포스트가 연결된 Like 레코드를 생성하고, true이면 삭제합니다.

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

// 요청자와 매개값으로 받은 포스트가 연결된 다수의(??) Like 레코드 삭제
await prisma.like.deleteMany(filterOptions);

// 요청자와 매개값으로 받은 포스트가 연결된 Like 레코드를 생성
await prisma.like.create({
    data: {
        user: {
            connect: {
                id: me.id
            }
        },
        post: {
            connect: {
                id: postId
            }
        }
    }
});

다수의 Like 레코드를 삭제하는 이유는 무엇인가요?

두 가지 이유가 있습니다.

  • 백앤드가 서버리스(AWS 람다) 환경에서 실행되기 때문에, 타임아웃이 발생하면 다수의 동일한 Like 레코드가 생성될 수 있습니다.
  • Post 컴포넌트에서 랜더링 관련 실수를 하면 다수의 동일한 Like 레코드가 생성될 수 있습니다.

아직 백앤드에서 오류가 발생한 적은 없습니다. Post 컴포넌트에서 발생할 수 있는 랜더링 실수는 다음 포스트에서 설명하겠습니다.

 

마지막으로  매개값으로 받은 포스트 ID를 사용하여 해당 Post 레코드를 검색하여 반환합니다.

toggleLike Resolver를 정의할 때 Post를 반환하도록 했기 때문에 이렇게 한 것이지 Boolean을 반환하도록 정의했다면, 굳이 데이터베이스에 다시 접근할 필요가 없습니다.

return prisma.post.findOne({
    where: {
        id: postId
    }
})