DB, ORM/🧊 typeORM

graphql (to) typeORM Entity (to) resolvers.ts

DarrenKwonDev 2020. 10. 27. 04:37

graphql을 백엔드에서 사용하려면 DB 모델과 연관시켜야 할터인데 이 작업이 조금 반복적인 작업이라서 하다보면 헷갈릴 때가 종종 있다. 나중에 참고하기 위해 기록을 남겨본다.

 

1. 우선 프로젝트를 분석하여 graphql Type을 만든 후 해당 graphql type을 이용하여 TypeORM에서 사용할 Entity로 만든다. class로 entity를 정의하므로 ts class의 단짝친구인 class-validator도 곁들여 활용하면 좋다.

 

type User {
  id: Int!
  email: String
  verifiedEmail: Boolean!
  firstName: String!
  lastName: String!
  age: Int
  password: String
  phoneNumber: String
  verifiedPhonenNumber: Boolean!
  profilePhoto: String
  fullName: String
  isDriving: Boolean!
  isRiding: Boolean!
  isTaken: Boolean!
  lastLng: Float
  lastLat: Float
  lastOrientation: Float
  createdAt: String!
  updatedAt: String
}
import { IsEmail } from "class-validator";
import bcrypt from "bcrypt";
import {
  BaseEntity,
  BeforeInsert,
  BeforeUpdate,
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn
} from "typeorm";

const BCRYPT_ROUNDS = 10;

@Entity()
class User extends BaseEntity {
  @PrimaryGeneratedColumn() id: number;

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

  @Column({ type: "boolean", default: false })
  verifiedEmail: boolean;

  @Column({ type: "text" })
  firstName: string;

  @Column({ type: "text" })
  lastName: string;

  @Column({ type: "int" })
  age: number;

  @Column({ type: "text" })
  password: string;

  @Column({ type: "text" })
  phoneNumber: string;

  @Column({ type: "boolean", default: false })
  verifiedPhonenNumber: boolean;

  @Column({ type: "text" })
  profilePhoto: string;

  @Column({ type: "boolean", default: false })
  isDriving: boolean;

  @Column({ type: "boolean", default: false })
  isRiding: boolean;

  @Column({ type: "boolean", default: false })
  isTaken: boolean;

  @Column({ type: "double precision", default: 0 })
  lastLng: number;

  @Column({ type: "double precision", default: 0 })
  lastLat: number;

  @Column({ type: "double precision", default: 0 })
  lastOrientation: number;

  @CreateDateColumn() createdAt: string;
  @UpdateDateColumn() updatedAt: string;

  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }

  private hashPassword(password: string): Promise<string> {
    return bcrypt.hash(password, BCRYPT_ROUNDS)
  }

  @BeforeInsert()
  @BeforeUpdate()
  async savePassword(): Promise<void> {
    if (this.password) {
      const hashedPassword = await this.hashPassword(this.password)
      this.password = hashedPassword
    }
  }

  // 비밀번호 일치 확인
  public comparePassword(password: string): Promise<boolean> {
    return bcrypt.compare(password, this.password);
  }
}

export default User;

 

관계를 설정하는 방법은 작성한 포스트를 참고합시다.

darrengwon.tistory.com/898

 

tppeORM relationship 설정하기

실습 세팅 관계형 DB가 그렇듯 다음과 같은 관계를 형성할 수 있다. Many-to-One and One-to-Many One-to-One many-to-many 우선 실습을 위한 관계형 테이블을 만들기 위해 아래 툴을 이용해보자. 전반적인 감을.

darrengwon.tistory.com

 

2. gql-merge와 type을 붙이기 위한 graphql-to-typescript를 활용하여 다음과 같이 작업해줍시다.

해당 작업의 결과로 ./src/types/graph.d.ts가 생성됩니다.

"scripts": {
  "pretypes": "gql-merge --out-file ./src/schema.graphql ./src/api/**/*.graphql",
  "types": "graphql-to-typescript ./src/schema.graphql ./src/types/graph.d.ts",
  "predev": "yarn run types",
  "dev": "cd src && nodemon --exec ts-node index.ts -e ts,graphql,json"
},

 

3. 이제 만들어진 타입을 이용하여 resolvers를 작성하면 됩니다.

 

테이블과 연계해서 특정 로직을 수행해야 하는 경우 Mongoose를 사용했던 것처럼 해당 테이블을 가져와서 save(), creat(), findOne() 등을 할 수 있습니다. 편하군요.

import User from "../../../entities/User";
import { Resolvers } from "src/types/resolvers";
import { FacebookConnectResponse, FacebookConnectMutationArgs } from "../../../types/graph";

const resolvers: Resolvers = {
  Mutation: {
    FacebookConnect: async (_, args: FacebookConnectMutationArgs): Promise<FacebookConnectResponse> => {
      // 이미 같은 fbId를 가진 유저가 있는지 체크함 (1번째 try/catch)
      try {
        // 동일한 fbId를 가진 유저가 있는지 체크
        const existingUser = await User.findOne({ fbId: args.fbId });
        if (existingUser) {
          return {
            ok: true,
            error: null,
            token: "Comming soon!, already existed",
          };
        }
      } catch (error) {
        return {
          ok: false,
          error: error.message,
          token: null,
        };
      }

      // 이미 존재하는 유저가 없으므로 새로 만들어야 함 (2번째 try/catch)
      try {
        await User.create({
          ...args,
          profilePhoto: `https://graph.facebook.com/${args.fbId}/picture?type=square`,
        }).save();

        return {
          ok: true,
          error: null,
          token: "Comming soon!, created",
        };
      } catch (error) {
        return {
          ok: false,
          error: error.message,
          token: null,
        };
      }
    },
  },
};

export default resolvers;