쿠팡이츠 간단한 CRUD 구현하기

shooting star·2024년 5월 10일
0
post-thumbnail

들어가며

이번 포스트에서는 Nest.jsGraphQL을 활용하여 사용자(User)와 관련된 간단한 CRUD API를 구축하는 방법에 대해 소개하려고 합니다. 이번 튜토리얼은 Nest.js의 구조와 GraphQL의 쿼리 언어를 이해하고, 간단한 계정 관리 기능을 직접 만들어보는 것을 목표로 합니다.

프로젝트 개요

  • GraphQL: 클라이언트가 원하는 데이터만 선택적으로 요청할 수 있는 쿼리 언어
  • Nest.js: 효율적인 서버 애플리케이션을 만들 수 있는 Node.js 프레임워크
  • TypeORM: 데이터베이스와의 상호작용을 돕는 ORM(Object Relational Mapping) 도구

이번 프로젝트에서는 사용자 관리 기능을 제공하는 모듈인 UsersModule을 구성하고, GraphQL을 통해 계정 생성, 로그인, 프로필 조회 및 편집 등의 기능을 구현해 보겠습니다.

핵심 모듈 구성과 기능 구현

1. UsersModule 정의

UsersModule은 사용자 관련 기능을 제공하는 모듈로, TypeOrmModule을 사용하여 User 엔터티를 데이터베이스와 연동하고 UsersServiceUsersResolver를 제공합니다.

// 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 {}

2. GraphQL 리졸버 (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;
}

3. 사용자 비즈니스 로직 (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: '로그인할 수 없습니다.' };
  }
}

4. 사용자 엔터티 (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.jsGraphQL을 활용하여 사용자 관리 기능을 제공하는 간단한 CRUD API를 구현해보았습니다. 핵심 기능을 간략히 정리하면 다음과 같습니다.

  • 모듈 구성: 모듈을 통해 기능별 컴포넌트를 관리
  • GraphQL 리졸버: 쿼리 및 뮤테이션을 처리하는 리졸버
  • 서비스 로직: 비즈니스 로직을 처리하는 서비스
  • 엔터티 구성: 데이터베이스 테이블을 매핑하는 엔터티

0개의 댓글