이번 포스트에서는 Nest.js와 GraphQL을 활용하여 사용자(User)와 관련된 간단한 CRUD API를 구축하는 방법에 대해 소개하려고 합니다. 이번 튜토리얼은 Nest.js의 구조와 GraphQL의 쿼리 언어를 이해하고, 간단한 계정 관리 기능을 직접 만들어보는 것을 목표로 합니다.
이번 프로젝트에서는 사용자 관리 기능을 제공하는 모듈인 UsersModule을 구성하고, GraphQL을 통해 계정 생성, 로그인, 프로필 조회 및 편집 등의 기능을 구현해 보겠습니다.
UsersModule
정의UsersModule은 사용자 관련 기능을 제공하는 모듈로, TypeOrmModule을 사용하여 User
엔터티를 데이터베이스와 연동하고 UsersService
와 UsersResolver
를 제공합니다.
// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UsersService } from './users.service';
import { UsersResolver } from './users.resolver';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService, UsersResolver],
exports: [UsersService],
})
export class UsersModule {}
UsersResolver
) 구현GraphQL 리졸버는 UsersService를 사용해 사용자와 관련된 쿼리 및 뮤테이션을 처리합니다. 주요 기능에 대해 살펴보겠습니다.
createAccount
)계정을 생성하기 위한 뮤테이션입니다. CreateAccountInput 타입의 DTO를 사용하여 입력을 받고, CreateAccountOutput 타입의 DTO를 사용하여 결과를 반환합니다.
@Mutation(returns => CreateAccountOutput)
async createAccount(
@Args('input') createAccountInput: CreateAccountInput,
): Promise<CreateAccountOutput> {
return this.usersService.createAccount(createAccountInput);
}
@InputType()
export class CreateAccountInput extends PickType(User, [
'email',
'password',
'role',
]) {}
@ObjectType()
export class CreateAccountOutput extends CoreOutput {
}
login
)사용자 인증을 처리하고 JWT 토큰을 반환하는 뮤테이션입니다. LoginInput 타입의 DTO를 사용하여 로그인 정보를 받고, LoginOutput 타입의 DTO를 사용하여 결과를 반환합니다.
@Mutation(returns => LoginOutput)
async login(
@Args('input') loginInput: LoginInput,
): Promise<LoginOutput> {
return this.usersService.login(loginInput);
}
@InputType()
export class LoginInput extends PickType(User, ['email', 'password']) { }
@ObjectType()
export class LoginOutput extends CoreOutput {
@Field(type => String, { nullable: true })
token?: string;
}
me
)현재 인증된 사용자의 정보를 반환하는 쿼리입니다.
@Query(returns => User)
@Role(['Any'])
me(@AuthUser() authUser: User) {
return authUser;
}
UsersService
) 구현UsersService는 사용자 계정 생성, 로그인, 프로필 조회 및 편집 등의 기능을 담당합니다.
createAccount
)새 계정 정보를 데이터베이스에 저장하고, 이메일 인증을 위한 Verification 엔터티를 생성합니다.
async createAccount({
email,
password,
role,
}: CreateAccountInput): Promise<CreateAccountOutput> {
try {
const exists = await this.users.findOne({ where: { email } });
if (exists) {
return { ok: false, error: '해당 이메일을 사용하는 사용자가 이미 있습니다.' };
}
const user = await this.users.save(
this.users.create({ email, password, role }),
);
return { ok: true };
} catch {
return { ok: false, error: '계정을 만들 수 없습니다.' };
}
}
login
)사용자의 이메일과 비밀번호를 확인하고, 인증이 성공하면 JWT 토큰을 반환합니다.
async login({ email, password }: LoginInput): Promise<LoginOutput> {
try {
const user = await this.users.findOne({
where: { email },
select: ['id', 'password'],
});
if (!user || !(await user.checkPassword(password))) {
return { ok: false, error: '잘못된 이메일 또는 비밀번호입니다.' };
}
const token = this.jwtService.sign(user.id);
return { ok: true, token };
} catch {
return { ok: false, error: '로그인할 수 없습니다.' };
}
}
User
)User 엔터티는 User
데이터베이스 테이블을 매핑하는 클래스입니다.
import { Field, InputType, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IsBoolean, IsEmail, IsEnum, IsString } from 'class-validator';
import { CoreEntity } from 'src/common/entities/core.entity';
import { BeforeInsert, BeforeUpdate, Column, Entity } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { InternalServerErrorException } from '@nestjs/common';
export enum UserRole {
Client = 'Client',
Owner = 'Owner',
Delivery = 'Delivery',
}
registerEnumType(UserRole, { name: 'UserRole' });
@InputType('UserInputType', { isAbstract: true })
@ObjectType()
@Entity()
export class User extends CoreEntity {
@Column({ unique: true })
@Field(type => String)
@IsEmail()
email: string;
@Column({ select: false })
@Field(type => String)
@IsString()
password: string;
@Column({ type: 'enum', enum: UserRole })
@Field(type => UserRole)
@IsEnum(UserRole)
role: UserRole;
@Column({ default: false })
@Field(type => Boolean)
@IsBoolean()
verified: boolean;
@BeforeInsert()
@BeforeUpdate()
async hashPassword(): Promise<void> {
if (this.password) {
try {
this.password = await bcrypt.hash(this.password, 10);
} catch (e) {
console.log(e);
throw new InternalServerErrorException();
}
}
}
async checkPassword(aPassword: string): Promise<boolean> {
try {
const ok = await bcrypt.compare(aPassword, this.password);
return ok;
} catch (e) {
console.log(e);
throw new InternalServerErrorException();
}
}
}
이번 포스트에서는 Nest.js와 GraphQL을 활용하여 사용자 관리 기능을 제공하는 간단한 CRUD API를 구현해보았습니다. 핵심 기능을 간략히 정리하면 다음과 같습니다.