본문 바로가기

백엔드/Prisma + GraphQL

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

GraphQL을 아직 잘 모르겠다면?

나의 첫번째 GraphQL서버 만들기

 

upload Resolver는 AWS S3 서비스와 연계되어 사용됩니다. S3에 관한 내용은 사진 업로드 구현 포스트에서 설명하겠습니다.

다음은 업로드 영상입니다.

 

upload.graphql

caption(내용) 문자열files 문자열 배열을 필수로 받아서 포스트를 생성하고, 생성된 포스트의 정보를 반환하는 Resolver입니다. 

files 배열의 요소는 사진 웹 경로입니다.

type Mutation {
    upload(caption: String!, files:[String!]!, location: String): Post!
}

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

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

사용자가 클라이언트(리액트 네이티브 앱)에서 여러 개의 사진을 선택후 업로드 버튼을 누르면, 클라이언트는 AWS S3에 사용자가 선택한 사진을 업로드하고, 각각의 사진 주소를 얻어옵니다. 사진의 주소를 배열로 만들어 upload 요청식을 백앤드에 전송합니다.

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

// GraphQL API 요청식
const UPLOAD = gql`
  mutation upload($caption: String!, $files: [String!]!, $location: String) {
    upload(caption: $caption, files: $files, location: $location) {
      id
      caption
      location
    }
  }
`;

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

let photoUrls = [];

// 사진을 AWS S3에 업로드 후 각각의 사진 URL을 photoUrls에 추가

// 사용자가 업로드 버튼을 누르면 핸들러에서 실행
// 매개값을 GraphQL API 요청 함수를 생성할 때가 아니라 실행할 때 입력
await uploadMutation({
    variables: {
        files: photoUrls,
        caption: captionInput.value,
        location: locationInput.value
    }
});

upload.js

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

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

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

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

            const { caption, files, location } = args;

            try {
                const post = await prisma.post.create({
                    data: {
                        caption,
                        location,
                        user: {
                            connect: {
                                id: me.id
                            }
                        }
    
                    }
                });

                
                files.forEach(
                    async file => {
                        await prisma.file.create({
                            data: {
                                url: file,
                                post: {
                                    connect: {
                                        id: post.id
                                    }
                                }
                            }
                        });
                    }
                )
                return post;
            } catch (e) {
                console.log(e);
                return e;
            }
        }
    }
}

exports.default = _default;

하나씩 알아볼까요?

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

참조1: Cognito ID 토큰 복호화

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

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

 

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

const { caption, files, location } = args;

 

먼저 Prisma 클라이언트를 사용해서 다수의 File 레코드가 연결Post 레코드를 생성합니다.

당연히 연결된 사용자는 요청자입니다.

const post = await prisma.post.create({
    data: {
        caption,
        location,
        user: {
            connect: {
                id: me.id // 요청자의 ID
            }
        }

    }
});

 

Prisma 클라이언트를 사용하여 클라이언트(앱)로 부터 받은 files 배열의 요소(이미지 URL)마다 File 레코드를 생성합니다.

당연히 연결된 포스트는 위에서 방금 생성한 Post 레코드입니다.

files.forEach(
    async file => {
        await prisma.file.create({
            data: {
                url: file,
                post: {
                    connect: {
                        id: post.id // 위에서 방금 생성한 Post
                    }
                }
            }
        });
    }
)

 

저의 경우 HeildSQL을 사용하여 데이터베이스를 확인해 보면 다음과 같이 나옵니다.

ID가 210과 211인 File이 ID가 86인 Post에 연결되었고, ID가 234과 235인 File이 ID가 96인 Post에 연결되어 있습니다.