NestJS 帶你飛! 시리즈 번역 30# 실전 응용(중)

2023. 7. 2. 23:24개발 문서 번역/NestJS

728x90
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다.

 

API 설계

앞에서 얘기했던것 처럼 이번에 구현할 시스템에서는
사용자(user)와 투두 리스트(todo) 두개의 큰 자원으로 분류할 수 있습니다.

실제로 API의 관점에서 보면 인증이라는 자원이 하나 더 있는데,
이는 로그인과 회원가입과 관련된 인증(Authentication)을 의미합니다.

 

인증

인증을 구현하기 위해 Authentication와 관련된 기술을 활용하겠습니다.

 

알림: Authentication와 관련된 기술들은 DAY23 - Authentication (상)DAY24 - Authentication (하) 를 참고 해주세요.

 

Guards

먼저 로컬 인증 전략과 JWT에서 사용될 Guard를 패키징 하겠습니다.

src/core/guards 아래 jwt-auth.guard.tslocal-auth.guard.ts를 생성하겠습니다.

$ nest generate guard core/guards/jwt-auth
$ nest generate guard core/guards/local-auth

 

JwtAuthGuard의 내용을 수정하겠습니다.

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

LocalAuthGuard의 내용을 수정하겠습니다.

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

 

index.ts를 생성하여 내보내기 경로를 관리하겠습니다.

알림: Guard의 기능은 DAY 13 - Guard를 참고해주세요.

 

사용자 모듈

사용자 정보를 얻기 위한 작업을 제공하기 위해 UserModuleUserService를 생성합니다.

$ nest generate module features/user
$ nest generate service features/user

 

생성이 완료될 때까지 기다리는 동안에,
먼저 사용자 비밀번호를 처리할 때 메서드를 하나 설계하여 솔팅 암호화를 진행했던 때를 기억해야 합니다.

먼저 src/core/utils 폴더 아래 common.utility.ts를 생성하겠습니다.

import { randomBytes, pbkdf2Sync } from 'crypto';

export class CommonUtility {
  public static encryptBySalt(
    input: string,
    salt = randomBytes(16).toString('hex'),
  ) {
    const hash = pbkdf2Sync(input, salt, 1000, 64, 'sha256').toString('hex');
    return { hash, salt };
  }
}

 

그리고

UserModule의 내용을 수정하여 UserService를 내보내고
MongooseModule을 가져와 UserModel을 생성하여 MongoDB의 사용자 Collection을 조작해야 하고 전달해야할
Definition은 UserDefinition입니다.

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserDefinition } from '../../common/models/user.schema';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [MongooseModule.forFeature([UserDefinition])],
  providers: [UserService],
  exports: [UserService],
})
export class UserModule {}

 

먼저 회원가입시 사용될 CreateUserDto를 생성하겠습니다.

src/features/user/dto 폴더 아래에 create-user.dto.ts를 생성하겠습니다.

import { IsEmail, IsEnum, MaxLength, MinLength } from 'class-validator';
import {
  USER_PASSWORD_MAX_LEN,
  USER_PASSWORD_MIN_LEN,
  USER_USERNAME_MAX_LEN,
  USER_USERNAME_MIN_LEN,
} from '../../../common/constants/user.const';
import { Role } from '../../../common/enums/role.enum';

export class CreateUserDto {
  @MinLength(USER_USERNAME_MIN_LEN)
  @MaxLength(USER_USERNAME_MAX_LEN)
  public readonly username: string;

  @MinLength(USER_PASSWORD_MIN_LEN)
  @MaxLength(USER_PASSWORD_MAX_LEN)
  public readonly password: string;

  @IsEmail()
  public readonly email: string;

  @IsEnum(Role)
  public readonly role: Role;
}

 

UserService의 내용을 수정하여 @InjectModel 데코레이터에 USER_MODEL_TOKEN을 지정해주고

UserModel을 주입하겠습니다.

import { Injectable } from '@nestjs/common';
import {
  USER_MODEL_TOKEN,
  UserDocument,
} from '../../common/models/user.schema';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(USER_MODEL_TOKEN)
    private readonly userModel: Model<UserDocument>,
  ) {}
}

이 시스템의 회원가입 API 목적은 기본 사용자를 등록하는데 있고,
이 기본 사용자를 통해 다른 사용자를 추가하는데 사용됩니다.
회원가입 API는 어떠한 사용자도 없을 경우에만 정상적으로 동작하여야 한다는 의미입니다.

그래서 UserServicecreateUser 메서드로 사용자를 생성하는것 외에 추가로 hasUser 메서드를 통해 해당 유저가 데이터베이스에 존재 하는지를 체크해야 합니다.

또 로그인 시 인증을 위해 서로 정보를 대조해보아야 하니 findUser 메서드 또한 필요하겠네요. 

import { Injectable } from '@nestjs/common';
import {
  USER_MODEL_TOKEN,
  UserDocument,
} from '../../common/models/user.schema';
import { InjectModel } from '@nestjs/mongoose';
import { FilterQuery, Model } from 'mongoose';
import { CreateUserDto } from './dto/create-user.dto';
import { CommonUtility } from '../../common/utils/common.utility';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(USER_MODEL_TOKEN)
    private readonly userModel: Model<UserDocument>,
  ) {}

  public async createUser(user: CreateUserDto) {
    const { username, email, role } = user;
    const password = CommonUtility.encryptBySalt(user.password);
    const document = await this.userModel.create({
      username,
      password,
      email,
      role,
    });
    return document?.toJSON();
  }

  public async findUser(filter: FilterQuery<UserDocument>, select?: any) {
    const query = this.userModel.findOne(filter).select(select);
    const document = await query.exec();
    return document?.toJSON();
  }

  public async hasUser() {
    const count = await this.userModel.estimatedDocumentCount().exec();
    return count > 0;
  }
}

 

index.ts를 생성하여 내보내기 경로를 관리하겠습니다.

export { UserModule } from './user.module';
export { UserService } from './user.service';
export { CreateUserDto } from './dto/create-user.dto';

 

인증 모듈

UserService를 통해 사용자 데이터를 조작할수 있게 되었습니다. 그럼 이제 인증 부분을 구현할 순서입니다.
관련된 컴포넌트들을 생성하겠습니다.

$ nest generate module features/auth
$ nest generate service features/auth
$ nest generate controller features/auth

 

 

이전에 언급한 Passport를 기억하고 계신가요?
인증에 통과하고 나면 일부 데이터가 요청 객체에 넣어져 후속 작업에서 해당 데이터를 사용할 수 있도록 해야 합니다.
이를 페이로드(Payload, 載體)라고 합니다.

일반적으로 사용자 정보를 페이로드에 포함시킵니다.

따라서 src/features/auth/interfaces 폴더 아래 payload.interface.ts를 만들어 페이로드의 인터페이스를 정의합니다.

주요 포함하는 자료로는 id, username, role이 있습니다. 

import { Role } from '../../../common/enums/role.enum';

export interface UserPayload {
  id: string;
  username: string;
  role: Role;
}

 

페이로드 내용을 얻기 위해 데코레이터를 하나 설계해보겠습니다.

src/features/auth/decorators 폴더 아래 payload.decorator.ts를 생성하겠습니다.

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: unknown, context: ExecutionContext) => {
    const request: Express.Request = context.switchToHttp().getRequest();
    return request.user;
  },
);

 

AuthModule을 수정하여 PassportModuleJwtModule을 가져와 완전한 로그인과 회원가입 인증 기능을 구현해야 합니다.

또한 ConfigService에서 관련 환경변수를 가져오고, UserModule을 사용하여 사용자 데이터를 조작할 수 있도록 합니다.

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { UserModule } from '../user';

@Module({
  imports: [
    PassportModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => {
        const secret = config.get('secrets.jwt');
        return {
          secret,
          signOptions: {
            expiresIn: '3600s',
          },
        };
      },
    }),
    UserModule,
  ],
  providers: [AuthService],
  controllers: [AuthController],
})
export class AuthModule {}

 

이어서 AuthService를 수정해 validateUser을 설계하여 유효한 사용자인지 검사 하고
generateJwt를 설계해 token을 통해 자원에 접근할 수 있도록 JWT를 생성해줍니다.

import { Injectable } from '@nestjs/common';
import { UserService } from '../user';
import { JwtService } from '@nestjs/jwt';
import { CommonUtility } from '../../common/utils/common.utility';
import { UserPayload } from './interfaces/payload.interface';

@Injectable()
export class AuthService {
  constructor(
    private readonly userService: UserService,
    private readonly jwtService: JwtService,
  ) {}

  public async validateUser(username: string, password: string) {
    const user = await this.userService.findUser({ username });
    const { hash } = CommonUtility.encryptBySalt(password, user?.password.salt);
    if (!user || hash !== user?.password?.hash) {
      return null;
    }
    return user;
  }
  public generateJwt(payload: UserPayload) {
    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}

AuthService를 완성한 후에 관련 인증 전략을 구현해야만 완벽한 Passport의 인증 로직이 완성 되는 것입니다.

먼저 LocalStrategy로 로그인할 때의 사용할 인증 전략을 처리합니다.
src/features/auth/strategies 폴더에 local.strategy.ts를 생성하겠습니다.

 

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';

import { UserPayload } from '../interfaces/payload.interface';

import { AuthService } from '../auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    super();
  }

  public async validate(username: string, password: string) {
    const user = await this.authService.validateUser(username, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    const payload: UserPayload = {
      id: user._id,
      username: user.username,
      role: user.role,
    };
    return payload;
  }
}

 

로그인 시 사용할 전략을 완성했으면 JWT의 유효성을 검사하는 로그인 기간의 인증 전략을 설계해야 합니다.

src/features/auth/strategies 아래 jwt.strategy.ts를 생성하겠습니다.

 

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';

import { ExtractJwt, Strategy } from 'passport-jwt';

import { UserPayload } from '../interfaces/payload.interface';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get('secrets.jwt'),
    });
  }

  validate(payload: UserPayload) {
    return payload;
  }
}

 

검증 전략을 설계한 뒤에 AuthModule 아래 providers를 추가해줍니다.

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';

import { UserModule } from '../user';

import { LocalStrategy } from './strategies/local.strategy';
import { JwtStrategy } from './strategies/jwt.strategy';

import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';

@Module({
  imports: [
    PassportModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => {
        const secret = config.get('secrets.jwt');
        return {
          secret,
          signOptions: {
            expiresIn: '3600s',
          },
        };
      },
    }),
    UserModule,
  ],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  controllers: [AuthController],
})
export class AuthModule {}

 

마지막으로 AuthController를 수정하여 signupsignin을 구현해 회원가입과 로그인 효과를 내보겠습니다.

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

import { LocalAuthGuard } from '../../core/guards';

import { CreateUserDto, UserService } from '../user';

import { User } from './decorators/payload.decorator';
import { UserPayload } from './interfaces/payload.interface';

import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
  constructor(
    private readonly authService: AuthService,
    private readonly userService: UserService,
  ) {}

  @Post('/signup')
  async signup(@Body() dto: CreateUserDto) {
    const hasUser = await this.userService.hasUser();
    if (hasUser) {
      throw new ForbiddenException();
    }
    const user = await this.userService.createUser(dto);
    const { _id: id, username, role } = user;
    return this.authService.generateJwt({ id, username, role });
  }

  @UseGuards(LocalAuthGuard)
  @Post('/signin')
  signin(@User() user: UserPayload) {
    return this.authService.generateJwt(user);
  }
}

 

index.ts를 생성하여 내보내기 경로를 관리하겠습니다.

export { AuthModule } from './auth.module';
export { UserPayload } from './interfaces/payload.interface';
export { User } from './decorators/payload.decorator';

 

사용자

사용자와 관련있는 API는 4개이며, 4개의 API를 설계하도록 하겠습니다.

  • [GET] /users : 사용자 리스트를 불러옵니다.
  • [POST] /users : 새로운 사용자를 등록합니다.
  • [DELETE] /users/:id : 특정한 사용자를 삭제합니다.
  • [PATCH] /users/:id : 특정한 사용자를 업데이트합니다.

사용자를 업데이트하는 부분은 DTO를 하나 설계하겠습니다.

src/features/user/dto 폴더 아래 update-user.dto.ts를 만들고 PartialType을 통해 CreateUserDto의 속성들을 상속 받겠습니다.

import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}

 

또한 사용자 목록을 가져오는 부분은 한 번에 가져올 수 있는 최대값과 건너뛸 데이터의 수를 지정해야 하기 때문에

SearchDto로 관련있는 파라미터들을 지정하겠습니다.

이를 위해서는 src/core/basessearch.dto.ts를 추가해야 합니다.

import { IsOptional } from 'class-validator';

export class SearchDto {
  @IsOptional()
  skip?: number;

  @IsOptional()
  limit?: number;
}

 

index.ts를 생성하여 내보내기 경로를 관리하겠습니다.

export { SearchDto } from './search.dto';

 

주의: SearchDtocore/bases 폴더 아래에 배치하는 이유는 다른 API가 더 많은 확장 가능한 공간을 가지게 하고
해당 DTO를 상속하게끔 하여 구현할 수 있도록 하기 위함입니다. 

 

입력된 limitskip을 제한하고, 기본값을 설정하기 위해 Pipe를 하나 설계해 보겠습니다.

/src/core/pipes 폴더에 search.pipe.ts를 생성하겠습니다.

import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class SearchPipe implements PipeTransform<Record<string, any>> {
  private readonly DEFAULT_LIMIT = 30;
  private readonly MAX_LIMIT = 50;
  private readonly DEFAULT_SKIP = 0;

  transform(value: Record<string, any>, metadata: ArgumentMetadata) {
    const { limit, skip } = value;
    value.limit = this.setLimit(parseInt(limit));
    value.skip = this.setSkip(parseInt(skip));
    return value;
  }

  private setLimit(limit: number): number {
    if (!limit) {
      return this.DEFAULT_LIMIT;
    }
    if (limit > this.MAX_LIMIT) {
      return this.MAX_LIMIT;
    }
    return limit;
  }

  private setSkip(skip: number): number {
    if (!skip) {
      return this.DEFAULT_SKIP;
    }
    return skip;
  }
}

index.ts를 생성하여 내보내기 경로를 관리하겠습니다.

export { SearchPipe } from './search.pipe';

 

이어서 UserService의 내용을 수정하겠습니다.
사용자 목록을 가져오는 findUsers, 사용자를 삭제하는 deleteUser, 사용자 정보를 업데이트하는 updateUser,
중복 등록된 사용자를 확인하는 existUser 메서드를 추가 하겠습니다.

 

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { FilterQuery, Model } from 'mongoose';

import { CommonUtility } from '../../core/utils/common.utility';
import { SearchDto } from '../../core/bases';

import { USER_MODEL_TOKEN, UserDocument } from '../../common/models/user.model';

import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UserService {
  constructor(
    @InjectModel(USER_MODEL_TOKEN)
    private readonly userModel: Model<UserDocument>,
  ) {}

  public async createUser(user: CreateUserDto) {
    const { username, email, role } = user;
    const password = CommonUtility.encryptBySalt(user.password);
    const document = await this.userModel.create({
      username,
      password,
      email,
      role,
    });
    return document?.toJSON();
  }

  public async findUser(filter: FilterQuery<UserDocument>, select?: any) {
    const query = this.userModel.findOne(filter).select(select);
    const document = await query.exec();
    return document?.toJSON();
  }

  public async findUsers(search: SearchDto, select?: any) {
    const { skip, limit } = search;
    const query = this.userModel.find().select(select);
    const documents = await query.skip(skip).limit(limit).exec();
    return documents.map((document) => document?.toJSON());
  }

  public async deleteUser(userId: string) {
    const document = await this.userModel.findByIdAndRemove(userId).exec();
    if (!document) {
      return;
    }
    return {};
  }

  public async updateUser(userId: string, data: UpdateUserDto, select?: any) {
    const obj: Record<string, any> = { ...data };
    if (obj.password) {
      obj.password = CommonUtility.encryptBySalt(obj.password);
    }
    const query = this.userModel
      .findByIdAndUpdate(userId, obj, { new: true })
      .select(select);
    const document = await query.exec();
    return document?.toJSON();
  }

  public existUser(filter: FilterQuery<UserDocument>) {
    return this.userModel.exists(filter);
  }

  public async hasUser() {
    const count = await this.userModel.estimatedDocumentCount().exec();
    return count > 0;
  }
}

CLI로 UserController를 생성해줍니다.

$ nest generate controller features/user

UserController를 수정하여 API 요구에 맞게끔 설계합니다.

import {
  Body,
  ConflictException,
  Controller,
  Delete,
  ForbiddenException,
  Get,
  Param,
  Patch,
  Post,
  Query,
  UseGuards,
} from '@nestjs/common';

import { SearchPipe } from '../../core/pipes';
import { JwtAuthGuard } from '../../core/guards';
import { SearchDto } from '../../core/bases/dto';

import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

import { UserService } from './user.service';

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

  @Get()
  async getUsers(@Query(SearchPipe) query: SearchDto) {
    return this.userService.findUsers(query, '-password');
  }

  @Post()
  async createUser(@Body() dto: CreateUserDto) {
    const { username, email } = dto;
    const exist = await this.userService.existUser({
      $or: [{ username }, { email }],
    });

    if (exist) {
      throw new ConflictException('username or email is already exist.');
    }

    const user = await this.userService.createUser(dto);
    const { password, ...result } = user;
    return result;
  }

  @Delete(':id')
  async deleteUser(@Param('id') id: string) {
    const response = await this.userService.deleteUser(id);
    if (!response) {
      throw new ForbiddenException();
    }
    return response;
  }

  @Patch(':id')
  async updateUser(@Param('id') id: string, @Body() dto: UpdateUserDto) {
    const user = await this.userService.updateUser(id, dto, '-password');
    if (!user) {
      throw new ForbiddenException();
    }
    return user;
  }
}

 

투두 리스트

CLI로 관련 컴포넌트들을 생성하겠습니다.

$ nest generate module features/todo
$ nest generate service features/todo
$ nest generate controller features/todo

 

투두 리스트와 관련있는 API는 다음과 같으며, 4개의 API를 설계하도록 하겠습니다.

  • [GET] /todos : 투두 리스트를 불러옵니다.
  • [POST] /todos : 투두 리스트를 생성합니다.
  • [DELETE] /todos : 특정 투두 리스트를 삭제합니다.
  • [PATCH] /todos/:id : 특정 투두 리스트를 업데이트합니다.

투두 리스트를 생성하는 부분은 DTO를 하나 설계하겠습니다.

src/features/todo/dto 폴더 아래 create-todo.dto.tsupdate-todo.dto.ts를 만들어주겠습니다.

import { IsOptional, MaxLength, MinLength } from 'class-validator';
import {
  TODO_DESCRIPTION_MAX_LEN,
  TODO_TITLE_MAX_LEN,
  TODO_TITLE_MIN_LEN,
} from '../../../common/constants/todo.const';

export class CreateTodoDto {
  @MinLength(TODO_TITLE_MIN_LEN)
  @MaxLength(TODO_TITLE_MAX_LEN)
  public readonly title: string;

  @IsOptional()
  @MaxLength(TODO_DESCRIPTION_MAX_LEN)
  public readonly description?: string;

  @IsOptional()
  public readonly completed?: boolean;
}

 

TodoModuleMongooseModule을 가져와 TodoModel을 생성하면 이를 통해
MongoDB의 todos 컬렉션을 조작할 수 있게됩니다. 또한 이를 위해서 TodoDefinition을 전달해야 합니다.

 

이어서 TodoService의 내용을 수정하여 createTodo는 투두 리스트를 생성하고, findTodos는 투두 리스트의 목록을 불러오고, deleteTodo는 특정 투두 리스트를 삭제하고, updateTodo는 특정 투두 리스트를 업데이트 하도록 설계하겠습니다.

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';

import { SearchDto } from '../../core/bases';

import { TodoDocument, TODO_MODEL_TOKEN } from '../../common/models/todo.model';

import { CreateTodoDto } from './dto/create-todo.dto';
import { UpdateTodoDto } from './dto/update-todo.dto';

@Injectable()
export class TodoService {
  constructor(
    @InjectModel(TODO_MODEL_TOKEN)
    private readonly todoModel: Model<TodoDocument>,
  ) {}

  public async createTodo(data: CreateTodoDto) {
    const todo = await this.todoModel.create(data);
    return todo?.toJSON();
  }

  public async findTodos(search: SearchDto, select?: any) {
    const { skip, limit } = search;
    const query = this.todoModel.find().select(select);
    const documents = await query.skip(skip).limit(limit).exec();
    return documents.map((document) => document?.toJSON());
  }

  public async deleteTodo(todoId: string) {
    const document = await this.todoModel.findByIdAndRemove(todoId).exec();
    if (!document) {
      return;
    }
    return {};
  }

  public async updateTodo(todoId: string, data: UpdateTodoDto, select?: any) {
    const query = this.todoModel
      .findByIdAndUpdate(todoId, data, { new: true })
      .select(select);
    const document = await query.exec();
    return document?.toJSON();
  }
}

 

마지막으로 TodoController의 내용을 API 요구사항에 맞게끔 수정하겠습니다.

import {
  Body,
  Controller,
  Delete,
  ForbiddenException,
  Get,
  Param,
  Patch,
  Post,
  Query,
  UseGuards,
} from '@nestjs/common';

import { JwtAuthGuard } from '../../core/guards';
import { SearchPipe } from '../../core/pipes';
import { SearchDto } from '../../core/bases';

import { CreateTodoDto } from './dto/create-todo.dto';
import { UpdateTodoDto } from './dto/update-todo.dto';

import { TodoService } from './todo.service';

@UseGuards(JwtAuthGuard)
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @Get()
  async getTodos(@Query(SearchPipe) query: SearchDto) {
    return this.todoService.findTodos(query);
  }

  @Post()
  async createTodo(@Body() dto: CreateTodoDto) {
    return this.todoService.createTodo(dto);
  }

  @Delete(':id')
  async deleteTodo(@Param('id') id: string) {
    const response = await this.todoService.deleteTodo(id);
    if (!response) {
      throw new ForbiddenException();
    }
    return response;
  }

  @Patch(':id')
  async updateTodo(@Param('id') id: string, @Body() dto: UpdateTodoDto) {
    const todo = await this.todoService.updateTodo(id, dto);
    if (!todo) {
      throw new ForbiddenException();
    }
    return todo;
  }
}

 

마치며

오늘은  auth, todosusers를 포함한 API 구현이 모두 완료 되었습니다.

오늘 포스팅은 내용도 길고 작성해야할 것들도 많아 조금은 힘든 시간이었을겁니다.
하지만 이제는 역할과 권한 인증에 대한 부분만 남았으니 조금만 더 힘내봅시다!