본문 바로가기

개발새발 개발자/기타

[TypeGraphQL] User 등록하기

1. resolver 이동 및 수정

index.ts에 있던 resolver를 옮겨볼게요.

import {Mutation, Query, Resolver} from "type-graphql";

@Resolver()
export class RegisterResolver {
    // gql은 종종 쿼리나 스키마가 없을 때 까다로워지는 경우가 있어 아래는 사용은 안하지만 남겨놓는다.
    @Query(() => String, {name: 'helloWorld'})
    async hello() {
        return "hello world!";
    }

    @Mutation(() => String)
    async register() {
        return "hello world!";
    }
}

서버를 실행하고 스키마를 확인하면 정상적으로 mutation이 만들어진 걸 볼 수 있습니다.

2. @Arg 애너테이션으로 인자 추가

import {Arg, Mutation, Query, Resolver} from "type-graphql";

@Resolver()
export class RegisterResolver {
    @Query(() => String, {name: 'helloWorld'})
    async hello() {
        return "hello world!";
    }

    @Mutation(() => String)
    // @Arg('argument 이름 즉, graphql 스키마에서의 이름') 변수 이름: 타입
    async register(@Arg('firstName') firstName: string) {
        return firstName;
    }
}

argument를 받도록 설정해봅시다. @Arg 애너테이션 안에 graphql 스키마에서의 이름과 실제 함수가 사용할 변수 이름과 타입을 명시해줍니다.

 

스키마도 그대로 변경된 것을 볼 수 있어요.

3. resolver 로직 추가

import {Arg, Mutation, Query, Resolver} from "type-graphql";
import * as bcrypt from 'bcryptjs';
import {User} from "../../entity/User";

@Resolver()
export class RegisterResolver {
    @Query(() => String, {name: 'helloWorld'})
    async hello() {
        return "hello world!";
    }

    @Mutation(() => User) // 여기서의 타입은 스키마에서의 타입이다.
    async register(
        @Arg("firstName") firstName: string,
        @Arg("lastName") lastName: string,
        @Arg("email") email: string,
        @Arg("password") password: string
    ): Promise<User> {  // 함수의 리턴 타입일 뿐 스키마에 영향을 주는 것은 아니다.
        const hashedPassword = await bcrypt.hash(password, 12);

        const user = await User.create({
            firstName,
            lastName,
            email,
            password: hashedPassword
        }).save();

        return user;
    }
}

이제 entity에 정의해두었던 내용을 인자로 다 넣어주고, 비밀번호를 암호화한 뒤 새로운 유저를 생성합니다.

 

이때 리턴값은 Promise<User> 타입이 되는데요, 이 값은 함수가 리턴하는 타입일 뿐, 스키마와 관련있는 것은 아닙니다. 스키마에서의 타입은 @Mutation 애너테이션에 쓰여있는 User예요!

 

하지만 여기서 실행을 하면,

 

이렇게 와장창 에러가 뜰거예요 T.T @Mutation에 정의한 User 타입을 사용하려면 entity에도 설정을 해줘야 하거든요.

4. entity에 ObjectType 설정

import {BaseEntity, Column, Entity, PrimaryGeneratedColumn} from "typeorm";
import {Field, ObjectType} from "type-graphql";

// Register.ts의 함수에서 타입으로 사용하기 위해 ObjectType으로 지정해준다.
@ObjectType()
@Entity()
export class User extends BaseEntity {   
    // 쿼리에 허용할 필드에 @Field 애너테이션을 붙인다.
    @Field()
    @PrimaryGeneratedColumn()
    id: number;

    @Field()
    @Column()
    firstName: string;

    @Field()
    @Column()
    lastName: string;

    @Field()
    @Column("text", {unique: true})
    email: string;

    // password는 쿼리로 노출되면 안되므로 @Field 애너테이션을 붙이지 않는다.
    @Column()
    password: string;
}

먼저 @ObjectType 애너테이션을 붙여준 뒤, 쿼리에 허용할 필드에 @Field 애너테이션을 붙여주면 됩니다.

 

5. ID 타입 설정

보통 graphql은 typescript의 타입으로 선언된 타입을 보고 타입을 인식합니다. 하지만 모든 것을 다 처리할 수 있는 건 아니에요.

 

예를 들어 id 필드의 경우 number로 지정되어 있는데요, 이게 integer인지 double인지 확실하지가 않잖아요?! 그래서 type-graphql이 지원하는 별도의 타입으로 지정해줄 수 있습니다.

    @Field(() => ID)
    @PrimaryGeneratedColumn()
    id: number;

바로 이렇게요!

 

변경된 스키마를 확인해보세요. User에서 password는 제외된 걸 볼 수 있죠? 데이터베이스 필드는 맞지만 graphql 필드로는 지정되지 않은 것이죠!

6. name 필드에 graphql 타입 적용

graphql 타입만 사용해서 필드 타입을 지정할 수도 있습니다.

    @Field()
    name: string;

먼저 name을 entity에 추가해준 뒤에

 

@Resolver(User) // FieldResolver에서 사용할 타입을 넘긴다.
export class RegisterResolver {
    @Query(() => String, {name: 'helloWorld'})
    async hello() {
        return "hello world!";
    }

    @FieldResolver()
    // 최상단의 @Resolver에서 User를 넘기고 있으므로 User를 사용할 수 있게 되었다.
    // @Root 애너테이션을 이용해 parent에 User 타입을 지정한다.
    async name(@Root() parent: User) {
        return `${parent.firstName} ${parent.lastName}`;
    }

    @Mutation(() => User) 
    async register(
        ...
    }
}

@Resolver에 User 타입을 넣어주면 사용할 수 있게 되는데요, 이를 위해 name에 @FieldResolver를 붙이고 @Root 애너테이션을 붙입니다.

 

@Root 애너테이션을 붙일 때는 parent라는 이름을 사용해요. parent를 User 타입으로 지정해주면 User에 있는 내용을 그대로 불러오게 됩니다.

 

맨 아래에 name이 추가된 것을 보실 수 있습니다! 

6. 데모

register에 필요한 인자와 받고 싶은 결과값을 보내면, 정상적으로 처리된 걸 볼 수 있어요.

 

entity에 email 필드를 추가할 때 unique로 지정했던 것, 기억 나시나요? 이렇게 같은 email로 다시 등록하면 같은 내용이라면서 추가에 실패하고 에러 메시지가 뜨게 됩니다 :)