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;
관계를 설정하는 방법은 작성한 포스트를 참고합시다.
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;