nest js

NestJs 회원가입(비밀번호 암호화), 로그인 구현하기

호리둥절 2023. 4. 14. 10:55

인증관련부분은 다음시간에 구현하고, 먼저 회원가입과 로그인기능에 대해 구현해보겠습니다.

auth와 user 파일을 간단하게 생성

nest g resource auth

nest g resource user

user

[ user/entities/user.entitiy.ts ]

import {
  BeforeInsert,
  Column,
  CreateDateColumn,
  DeleteDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';

import * as bcrypt from 'bcrypt';

@Entity({ name: 'user' })
export class UserEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  email: string;

  @Column()
  password: string;

  @Column()
  nickname: string;

  @CreateDateColumn()
  created_at: Date;

  @UpdateDateColumn()
  updated_at: Date;

  @DeleteDateColumn()
  deleted_at: Date;

  @BeforeInsert()
  private beforeInsert() {
    this.password = bcrypt.hashSync(this.password, 10);
  }
}

@BeforeInsert() 데코레이터는 TypeORM에서 제공하는 데코레이터 중 하나이며, 데이터베이스에 새로운 엔티티가 저장되기 전에 자동으로 실행되는 메서드를 정의할 수 있습니다.

bcrypt.hashSync() 함수는 bcrypt 라이브러리에서 제공하는 함수 중 하나로, 주어진 문자열을 해시화하여 반환합니다. 이 함수는 동기식으로 작동하므로, 해시화 작업이 완료될 때까지 대기합니다.

 

[ user/user.service.ts ]

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { UserEntity } from 'src/user/entities/user.entity';
import { AuthDTO } from 'src/auth/dto/authDto';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(UserEntity)
    private userRepository: Repository<UserEntity>,
  ) { }

  async create(authDTO: AuthDTO.SignUp) {
    const userEntity = await this.userRepository.create(authDTO);
    return await this.userRepository.save(userEntity);
  }

  async findById(id: number) {
    return await this.userRepository.findOne({
      where: {
        id,
      },
    });
  }

  async findByEmail(email: string) {
    return await this.userRepository.findOne({
      where: {
        email,
      },
    });
  }

  async findByNickname(nickname: string) {
    return await this.userRepository.findOne({
      where: {
        nickname,
      },
    });
  }
}

[ user/user.controller.ts ]

import {
  Body,
  ConflictException,
  Controller, Post,
} from '@nestjs/common';

import { UserService } from './user.service';
import { AuthDTO } from 'src/auth/dto/authDto';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) { }

  @Post('/signup')
  async signup(@Body() authDTO: AuthDTO.SignUp) {

    const { email, nickname } = authDTO;

    const hasEmail = await this.userService.findByEmail(email);
    if (hasEmail) {
      throw new ConflictException('이미 사용중인 이메일 입니다.');
    }

    const hasNickname = await this.userService.findByNickname(nickname);
    if (hasNickname) {
      throw new ConflictException('이미 사용중인 닉네임 입니다.');
    }

    const userEntity = await this.userService.create(authDTO);

    return '회원가입성공';
  }
}

회원가입코드는 user crud와 관련된것이기 때문에 userController에서 작성해주었습니다.

throw error를 controller에서 처리하는 이유는, controller는 API 엔드포인트와 직접적으로 연결되어 있어서 클라이언트에게 에러 메시지를 응답으로 반환할 수 있기 때문입니다.

반면에, throw error를 service나 다른 모듈에서 처리하게 되면, 이 에러를 controller에서 캐치할 수 없기 때문에 클라이언트에게 적절한 응답을 보내줄 수 없습니다. 따라서 일반적으로 throw error는 controller에서 처리하는 것이 좋습니다.

 

[ user/user.module.ts ]

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { UserService } from './user.service';
import { UserController } from './user.controller';
import { UserEntity } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([UserEntity])], //userrepository사용할수있도록 주입
  exports: [UserService], //userService 다른곳에서 사용할수 있도록 exports
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

auth

인증에 관련한 것들은 auth에서 구현하였습니다.

[ auth/dto/authDto.ts ]

import { IsEmail,IsString ,Length} from "class-validator";
export namespace AuthDTO {
    export class SignUp {
      @IsEmail()
      email: string;
  
      @IsString()
      @Length(4, 20)
      password: string;
  
      @IsString()
      nickname: string;
    }
  
    export class SignIn {
      @IsEmail()
      email: string;
  
      @IsString()
      @Length(4, 20)
      password: string;
    }
  }

 

[ auth/auth.controller.ts ]

import {
  Controller,
  Post,
  Body,
  Get,
  Req,
  UseGuards,
  UnauthorizedException
} from '@nestjs/common';

import { JwtService } from '@nestjs/jwt/dist';
import * as bcrypt from 'bcrypt';

import { AuthDTO } from './dto/authDto';
import { UserService } from 'src/user/user.service';

@Controller()
export class AuthController {
  constructor(private readonly userService: UserService) { }

  @Post('/signin')
  async signin(@Body() authDTO: AuthDTO.SignIn) {
    const { email, password } = authDTO;

    const user = await this.userService.findByEmail(email);
    if (!user) {
      throw new UnauthorizedException('이메일 또는 비밀번호를 확인해 주세요.');
    }

    const isSamePassword = bcrypt.compareSync(password, user.password);
    if (!isSamePassword) {
      throw new UnauthorizedException('이메일 또는 비밀번호를 확인해 주세요.');
    }

    return "로그인 완료"
  }
}

bcrypt.compareSync() 함수는 bcrypt 라이브러리에서 제공하는 함수 중 하나로, 주어진 문자열과 해시화된 문자열을 비교하여 일치 여부를 반환합니다. 이 함수는 동기식으로 작동하므로, 비교 작업이 완료될 때까지 대기합니다.

 

[ auth.module.ts ]

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UserModule } from 'src/user/user.module';

@Module({
  imports: [UserModule], 
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

app

[ app.module.ts ]

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
import { UserModule } from './user/user.module';
import { UserEntity } from './user/entities/user.entity';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: process.env.DB_HOST,
      port: Number(process.env.DB_PORT),
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      entities: [`${__dirname}/**/entities/*.entity.{ts,js}`],
      synchronize: Boolean(process.env.DB_SYNC),
    }),
    AuthModule,
    UserModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

테스트

[ 회원가입 ]

[ 로그인 ]

[ mysql ]

다음시간에는 jwt 토큰 인증을 붙여보겠습니다!

 

user에서 crud 하는것은 userController에 작성하고 user인증하는 부분은 authController에 작성하였습니다.