본문 바로가기

백엔드/Prisma + GraphQL

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

GraphQL을 아직 잘 모르겠다면?

나의 첫번째 GraphQL서버 만들기

 

createAccount는 클라이언트에서 직접 호출하지 않고, 사용자가 Cognito에 가입하면 Cognito의 Pre-SignUp 트리거에서 호출됩니다.

참조: [인스타그램 클론코딩] Cognito Pre-SignUp 람다 함수

createAccount.graphql

userName과 email을 필수 매개값으로 받아서 문자열을 반환하는 Resolver입니다.

type Mutation {
    createAccount(
        userName: String!, 
        email: String!, 
        firstName: String, 
        lastName: String,
        name: String,
        bio: String,
        picture: String
    ): String
}

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

 

Cognito Pre-SignUp 트리거 핸들러

Pre-SignUp 트리거에서 사용된 createAccount 요청 코드입니다. createAccount는 문자열을 반환하기 때문에, 반환받을 필드를 지정해 줄 필요가 없습니다.

const { GraphQLClient } = require('graphql-request');

let firstName, lastName, name, picture = "";

// firstName, lastName, name, picture 변경

const createAccountMutation = `mutation{ 
    createAccount(
        userName: "${userName}", 
        email: "${email}", 
        firstName: "${firstName}", 
        lastName: "${lastName}", 
        name: "${name}", 
        picture: "${picture}"
    )
}`;

await graphQLClient.request(createAccountMutation);

참조: [인스타그램 클론코딩] Cognito Pre-SignUp 람다 함수

 

createAccount.js

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

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

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

const _default = {
    Mutation: {
        createAccount: async(_, args, { prisma }) => {
            try {
                const {userName, email, firstName = "", lastName = "", name="", bio="", picture="" } = args;
                const existEmail = await prisma.user.findOne({
                    where: {
                        email
                    }
                });

                if(existEmail !== null) {
                    return "ExistEmailError"
                }

                const existUserName = await prisma.user.findOne({
                    where: {
                        userName
                    }
                });

                if(existUserName !== null) {
                    return "ExistUserNameError"
                }

                await prisma.user.create({ 
                    data: {
                        userName, 
                        email, 
                        firstName, 
                        lastName, 
                        name,
                        bio,
                        avatar: picture
                    }
                });
                return "SignUpComplete";
            } catch (e) {
                console.log(e);
                return e;
            }
        }
    }
}

exports.default = _default;

하나씩 알아볼까요?

Pre-SignUp 트리거에서 createAccount를 요청할 때 입력한 매개변수를 createAccount Resolver의 두 번째 매개변수에서 추출할 수 있습니다.

const { userName, email, firstName = "", lastName = "", name="", bio="", picture="" } = args;

 

데이터베이스에서 동일한 email 또는 userName을 사용하는 사용자가 있는지 검사합니다. 동일한 email을 사용하는 사용자가 검색되면 "ExistEmailError" 문자열을 반환하고, 동일한 userName을 사용하는 사용자가 검색되면 "ExistUserNameError" 문자열을 반환합니다.

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

const existEmail = await prisma.user.findOne({
    where: {
        email
    }
});

if(existEmail !== null) {
    return "ExistEmailError"
}

const existUserName = await prisma.user.findOne({
    where: {
        userName
    }
});

if(existUserName !== null) {
    return "ExistUserNameError"
}

두 번에 걸쳐 검색하는 이유는 무엇인가요?

Prisma 데이터 모델링을 할 때 email과 userName 필드를 유니크(@unique) 속성으로 설정했었습니다.

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

 

findOne 메소드는 단 한 개의 unique 필드만 조건(where)으로 입력받을 수 있습니다.

그렇다면 email 또는 userName 둘 중 한 개만 검색해도 되는 것 아닌가요?

클라이언트에 정확히 무슨 이유때문에 가입이 안됐는지 알려주기 위해서 입니다.

물론 Pre-SignUp 람다 함수에서 Cognito 사용자를 검색하는 것만으로 필터링이 되겠지만, Cognito와 데이터베이스간의 연결이 너무 약해서(회원가입 및 탈퇴중에 람다 함수 오류 또는 시간 초과 등이 발생하면 예측할 수 없는 결과가 발생할 수 있음) 약간 무리해서라도 백앤드에서 다시 한 번 확인하도록 했습니다. 

참조: [인스타그램 클론코딩] Cognito Pre-SignUp 람다 함수

 

여기까지 도달한다면 Prisma 클라이언트를 사용하여 데이터베이스에 사용자 정보를 복제합니다. 이 경우 bio는 항상 ""이겠군요.

성공적으로 복제하면 "SignUpComplete" 문자열을 반환합니다.

await prisma.user.create({ 
    data: {
        userName, 
        email, 
        firstName, 
        lastName, 
        name,
        bio,
        avatar: picture
    }
});
return "SignUpComplete";