해당 포스팅은 nest.js 9.0.0 버전, typeorm 0.3.x 버전을 기준으로 작성되었습니다.
모든 글은 작성자의 주관이 100% 담겨있기 때문에 부정확할 수 있습니다.
#pre. 터미널을 켜고 프로젝트 폴더로 이동
위의 링크의 내용을 참고하여 study 폴더로 이동해줍니다.
그리고 code . 명령어를 통해 vscode를 열어줍니다.
이전 포스팅에서 로그인에 대한 간단한 개념을 배워보았습니다.
이번 포스팅에서는 Nest.js 프로젝트에서 로그인을 직접 구현 해보겠습니다.
JWT를 사용해서 구현을 해볼 계획입니다. 만약, Session을 이용해서 구현을 해보고 싶으신 분들은 아래 포스팅을 참고 바랍니다.
참고로...
로그인이 진짜 어렵습니다. 그럼에도 불구하고 처음에 로그인을 가르치는 이유는 일단, 유저를 DB에 등록해야 게시글을 만들든 댓글을 만들든 할 수 있기 때문입니다.
그리고 로그인 로직을 구현하다가 어려워서 포기하는 분들은 다음 걸 해도 포기하거나 설렁설렁할 분들이니, 포기할거면 여기서 포기하는게 낫다 라는 생각입니다. 로그인은 기본적이면서 필수적인 로직인데 이걸 포기한다면... 앞으로도 혼자서 알아가야할게 얼마나 많은데... 정말 큰일이라고 생각합니다... 그러니 웬만하면 포기하지말고 끝까지 해봅시다!
먼저, src폴더에 auth폴더를 만들고 그 안에 guards 폴더와 strategies 폴더를 만들겠습니다. 그리고 auth.module.ts와 auth.controller.ts, auth.service.ts도 만들어 줍시다.
그리고 AuthService, AuthController, AuthModule 순으로 기본 코드를 작성해줍시다.
// auth.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AuthService {
constructor() {}
}
// auth.controller.ts
import { Controller } from '@nestjs/common';
@Controller('auth')
export class AuthController {
constructor() {}
}
// auth.module.ts
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
@Module({
imports: [],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
그 후, AppModule의 imports에 AuthModule을 등록해줍시다.
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import * as path from 'path';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './res/user/user.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
console.log(`.env.${process.env.NODE_ENV}`);
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: `.env.${process.env.NODE_ENV}`,
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
retryAttempts: configService.get('NODE_ENV') === 'prod' ? 10 : 1,
type: 'mysql',
host: configService.get('DB_HOST'),
port: Number(configService.get('DB_PORT')),
database: configService.get('DB_NAME'),
username: configService.get('DB_USER'),
password: configService.get('DB_PASSWORD'),
entities: [path.join(__dirname, '/entities/**/*.entity.{js, ts}')],
synchronize: false,
logging: true,
timezone: 'local',
}),
}),
UserModule,
AuthModule, // 이부분
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
AuthService
1. Passport 모듈 설치
먼저, 로그인 구현을 위한 라이브러리를 설치합시다.
여기서는 Passport라는 모듈을 사용해서 로그인을 구현합니다.
프로젝트 최상위 폴더 (혹은 package.json이 있는 폴더)에서 터미널에 다음 명령어를 입력합니다.
npm i @nestjs/passport passport passport-local
npm i --save-dev @types/passport
npm i @nestjs/jwt passport-jwt
npm i --save-dev @types/passport-jwt
2. AuthService에 validate함수 작성
다음으로 사용자의 유효성을 검증하는 validate함수를 작성해보도록 하겠습니다.
AuthService에 다음과 같이 코드를 작성해줍시다.
// auth.service.ts
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UserEntity } from 'src/entities/user.entity';
import { Repository } from 'typeorm';
import { compare } from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
) {}
async validateUser(email: string, password: string) {
const user = await this.userRepository.findOne({
where: { email: email },
});
if (!user) {
throw new BadRequestException('이메일이 잘못되었습니다.');
}
// verify password
const isPasswordMatch = await compare(password, user.password);
if (!isPasswordMatch) {
throw new BadRequestException('비밀번호가 일치하지 않습니다.');
}
return {
id: user.id,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
};
}
}
하나씩 설명하겠습니다.
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
) {}
constructor(생성자)에 UserRepository를 주입하여 AuthService에서 UserRepository를 사용할 수 있도록 합니다.
async validateUser(email: string, password: string) {
validateUser라는 비동기(async)함수를 정의합니다. 해당 함수는 파라미터(인자)로 email과 password를 받습니다.
const user = await this.userRepository.findOne({
where: { email: email },
});
userRepository를 사용해 해당 유저가 있는지 findOne(데이터를 하나만 찾는다. 유저가 중복될 일이 없기 때문에)함수를 이용하여 사용자가 입력한 email에 해당하는 data를 user에 저장합니다. (find는 select문입니다)
user 변수에는 DB의 column들의 값이 객체 형태로 저장됩니다. (ex. user = { id: "1", email: "test@gmail.com", password: "hashedPassword")
if (!user) {
throw new BadRequestException('이메일이 잘못되었습니다.');
}
만약 선택된 유저가 없으면 이메일이 잘못되었다는 메시지와 함께 에러를 던집니다. 이후 코드는 실행되지 않습니다.
// verify password
const isPasswordMatch = await compare(password, user.password);
if (!isPasswordMatch) {
throw new BadRequestException('비밀번호가 일치하지 않습니다.');
}
유저가 있다면, 비밀번호가 유효한지 검증합니다. bcrypt의 compare함수를 이용해서 사용자가 입력한 password와 DB에서 찾은 user.password를 비교해서 일치하는지 확인합니다(이 부분은 bcrypt의 암호화와 compare에 대한 이해가 필요하니, 그냥 이런식으로 비밀번호를 검증하는구나~ 하고 넘어가면 됩니다)
만약 비밀번호가 일치하지 않는다면 비밀번호가 일치하지 않는다는 메시지와 함께 에러를 던집니다. 이후의 코드는 실행되지 않습니다.
import { compare } from 'bcrypt' 가 필요합니다.
return {
id: user.id,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
};
비밀번호까지 일치한다면, 유저정보를 리턴합니다. password는 검증할 때 빼고는 필요없으니, id와 email, createdAt, updatedAt만 넘겨줍시다. password까지 넘기면 도중에 탈취당할 위험성이 있어서 password는 넘기지 않습니다.
LocalStrategy
Passport에서는 로그인 방식에 따라 전략(Strategy)을 구분하여 사용합니다.
우리는 local login(평범하게 아이디와 비밀번호를 이용해서 로그인하는 것)을 구현할 것이므로 LocalStrategy를 생성해보겠습니다.
먼저, strategies 폴더 안에 local.strategy.ts 파일을 만들어줍시다.
그 후, 다음과 같이 코드를 짜줍시다.
// local.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from '../auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
constructor(private readonly authService: AuthService) {
super({
usernameField: 'email',
passwordField: 'password',
});
}
async validate(email: string, password: string) {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
여기서 주의할 점은 Strategy를 passport-local에서 import 해와야된다는 점입니다. passport-jwt에서 import 해오면 안됩니다.
하나씩 봐봅시다.
export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
LocalStrategy라는 클래스를 정의하는데, 해당 클래스를 PassportStrategy를 상속받습니다. PassportStrategy인데, local Strategy를 사용하고, 해당 전략의 이름(나중에 guard에서 사용됩니다)은 local 로 정합니다.
constructor(private readonly authService: AuthService) {
super({
usernameField: 'email',
passwordField: 'password',
});
}
생성자에 authService를 주입합니다. 위에서 정의한 validateUser함수를 통해서 유저 이메일과 비밀번호가 일치하는지 검증하기 위함입니다.
그리고 super는 상속받은 클래스의 생성자를 호출하는 함수인데, 유저아이디는 email이라는 변수를 사용할 것이고, 비밀번호는 password라는 변수를 사용하겠다고 선언합니다. 잘 모르겠으면 그냥 외웁시다!
async validate(email: string, password: string) {
validate라는 비동기(async)함수를 정의하고 파라미터로 email과 password를 받습니다.
const user = await this.authService.validateUser(email, password);
authService에서 validateUser함수를 통해 나온 결과값을 user 변수에 담습니다. 위에서 id와 email, createdAt, updatedAt을 객체로 보냈으니 해당 정보가 담겨있을 것입니다.
if (!user) {
throw new UnauthorizedException();
}
만약 user에 아무 값도 들어있지 않다면, 에러를 던집니다. 이 이후 코드는 실행되지 않습니다.
return user;
user가 존재한다면, user에 담긴 데이터를 리턴합니다.
AuthGuard
로그인 전략을 세웠으니, 해당 전략을 이용해서 출입을 통제할 경비(Guard)를 만들어 봅시다.
LocalAuthGuard를 만들건데, 여기서는 경비가 출입을 통제하는 역할이 아니라 신분증(통행증) 발급의 역할을 하는 경비입니다.
먼저, guards폴더에 local-auth.guard.ts 파일을 만들도록 합시다.
그 후, 다음과 같이 코드를 작성합시다.
// local-auth.guard.ts
import { AuthGuard } from '@nestjs/passport';
export class LocalAuthGuard extends AuthGuard('local') {}
음... 그냥 이렇게만 쓰면 됩니다. 외웁시다.
AuthGuard('local')에서 local 부분이, 아까 LocalStrategy extends PassportStrategy(Strategy, 'local')의 local 과 일치합니다. 즉, LocalStrategy를 사용한다고 선언한 것이라고 보면 됩니다.
JwtStrategy
다시 AuthService로
조금 많이.. 복잡하지만 한꺼번에 올리는 것 보다 순서대로 함수를 작성하는게 로직을 이해하는데 도움이 될 것같아서 이렇게 씁니다.
다시 AuthService로 가서 logIn 함수를 생성해줍시다.
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UserEntity } from 'src/entities/user.entity';
import { Repository } from 'typeorm';
import { compare } from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(UserEntity)
private readonly userRepository: Repository<UserEntity>,
private readonly jwtService: JwtService,
) {}
async validateUser(email: string, password: string) {
const user = await this.userRepository.findOne({
where: { email: email },
});
if (!user) {
throw new BadRequestException('이메일이 잘못되었습니다.');
}
// verify password
const isPasswordMatch = await compare(password, user.password);
if (!isPasswordMatch) {
throw new BadRequestException('비밀번호가 일치하지 않습니다.');
}
return {
id: user.id,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
};
}
async logIn(user) {
return {
accessToken: this.jwtService.sign(user),
};
}
}
생성자(constructor) 부분에 jwtService를 주입해주고, async logIn함수를 정의해줍시다.
logIn함수를 해석해보면, user객체를 받아서 서명(암호화, sign)을 하고 객체형태로 내보낸다는 뜻입니다.
여기서 jwtService를 이용해서 sign을 할 때, secretKey는 jwtModule에서 설정해준 secretKey를 사용합니다. (JwtModule은 포스팅 아래 부분에서 AuthModule에 import하면서 설정해줄겁니다.)
JwtStrategy 전략 생성
JwtStrategy는 Jwt를 검증할 때 사용합니다. 즉, 사용자가 로그인 되어있는지 안되어있는지 확인할 때 사용됩니다.
jwt 전략을 생성해봅시다. 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';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(private readonly configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
});
}
async validate(payload) {
const { iat, exp, ...user } = payload;
return user;
}
}
하나씩 차근차근 설명해보겠습니다.
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
JwtStrategy라는 클래스는 PassportStrategy를 상속받는데, Jwt Strategy(passport-jwt에서 import한 Strategy)를 사용하고, 이 전략의 이름은 'jwt'로 짓는다(guard에서 사용됩니다)는 뜻입니다.
이 때, 주의할 점은 Strategy는 passport-jwt에서 import해와야된다는 점입니다.
constructor(private readonly configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
});
}
해당 클래스에서 configService를 사용할 수 있도록 생성자(constructor)에 configService를 주입합니다.
secretOrKey에 비밀키를 대입해야하기 때문에 환경변수 사용을 위해 configService가 필요합니다.
ConfigService와 환경변수에 대해 기억이 나지 않으신다면, 다음 포스팅을 보고 오시기 바랍니다.
jwtFromRequest는 프론트가 보낸 요청의 HTTP Header에 담겨있는 JWT를 추출하기 위해 필요합니다. 일단 그냥 외웁시다.
ignoreExpiration은 jwt은 유효기간이 있는데, 그 유효기간을 무시할거냐고 묻는 속성입니다. 보안을 위해서는 무시하면 안되기 때문에 false로 설정해줍니다.
secretOrKey는 jwt를 암호화할 때 필요한 비밀값입니다. (비밀번호 암호화할 때 필요한 salt와 비슷하다고 생각하시면 됩니다) 해당 속성의 값은 환경변수에 저장하는게 좋습니다.
.env.dev 파일에 들어가서 다음과같이 설정해줍시다.
JWT_SECRET의 값이 꼭 secret일 필요는 없습니다. 사용자 임의대로 마음대로 설정해주시면 됩니다. 이 강의에서는 편의상 secret으로 사용하도록 하겠습니다.
async validate(payload) {
const { iat, exp, ...user } = payload;
return user;
}
validate함수를 정의합니다. jwtStrategy가 사용되면 기본적으로 validate를 함수를 거칩니다.
jwt를 super에서 복호화한 후, validate로 jwt를 payload라는 이름의 파라미터로 받아서 사용합니다.
const { iat, exp, ...user } 는 구조분해할당입니다. 궁금하면 검색해봅시다.
일단 위 함수의 뜻은 payload에서 user를 꺼내서 return을 한다는 뜻입니다.
JwtAuthGuard
이제, Jwt전략을 사용할 가드(경비)를 만들어봅시다.
guards 폴더에 jwt-auth.guard.ts 파일을 만듭시다.
코드는 다음과 같습니다.
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
앞으로 해당 가드를 컨트롤러의 함수에 붙이면, 해당 함수를 실행시키기 전에 jwt로 로그인이 되어있나 확인하는 작업을 거치게 됩니다.
의존성을 위한 AuthModule 설정
이전까지 만들었던 모든 전략들은 모두 Module에 등록해주어야 합니다.
그리고, 로그인시 jwt서명을 위해 JwtModule도 등록해주어야 합니다.
다음과 같이 코드를 작성합시다.
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserEntity } from 'src/entities/user.entity';
import { JwtModule } from '@nestjs/jwt';
import { LocalStrategy } from './strategies/local.strategy';
import { JwtStrategy } from './strategies/jwt.strategy';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
TypeOrmModule.forFeature([UserEntity]),
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
return {
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: '30m' },
};
},
}),
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy],
})
export class AuthModule {}
먼저, UserEntity를 AuthService에서 사용하기 때문에 TypeOrmModule로 UserEntity를 등록해주어야합니다.
이제부터, JwtModule부터 하나씩 설명하겠습니다.
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
return {
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: '30m' },
};
},
}),
secret은 로그인시, jwt에 서명(sign)할 때 사용되는 secret키를 말합니다. JwtStrategy에서 사용되었던 secretOrKey와 동일한 값을 가져야합니다. JwtStrategy에서 사용된 secretOrKey는 복호화할 때 필요한 것이고, 여기서 사용되는 secret은 암호화할 때 필요합니다.
signOptions는 sign시 설정할 옵션을 말합니다. expiresIn: '30m' 은 jwt의 유효기간을 30분으로 설정하겠다는 뜻입니다. 아까, JwtStrategy에서 ignoreExpiration 옵션을 false로 해주었으니, 30분이 지나면 재로그인을 해주어야합니다.
providers: [AuthService, LocalStrategy, JwtStrategy],
그리고, providers에 LocalStrategy와 JwtStrategy를 등록하면 끝입니다.
Controller에 Guard 설정
자, 로그인에 관한 설정 파일들을 모두 만들었습니다. 이제는 만든 Guard를 Controller에 달아주면 됩니다.
AuthController에 다음과 같이 코드를 작성해줍시다.
// auth.controller.ts
import { Controller, Post, Req, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LocalAuthGuard } from './guards/local-auth.guard';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@UseGuards(LocalAuthGuard)
@Post('login')
async logIn(@Req() req) {
const user = req?.user;
console.log('user : ', user);
return this.authService.logIn(user);
}
}
@UseGuards 데코레이터를 이용해 LocalAuthGuard를 붙여줍니다.
로그인 시 LocalAuthGuard를 거치게 되면, passport(로그인을 위해 다운받은 패키지)에 의해 Request에 user객체가 자동으로 생성됩니다. 그리고 그 안에 user에 대한 정보(LocalAuthGuard를 거치면, AuthService의 validateUser함수를 거치는데 이 때, DB에서 user정보를 가져와서 return하니까...)가 담깁니다. 그 user를 req.user로 가져올 수 있습니다.
그리고 아까 만들어두었던 authService의 logIn함수를 실행시켜서 암호화된 jwt를 클라이언트쪽으로 보내줍니다.
로그인 해보기
자 그럼 로그인 로직을 완성했습니다.
터미널에서 npm run start:dev 명령어로 서버를 실행시켜봅시다.
항상 말하지만, 터미널에서 명령어를 칠 때는 현재 작업중인 폴더를 잘 확인해야합니다.
위의 사진을 보면 저의 경우 작업중인 폴더 경로가 ~/Documents/github/nest/study 입니다.
그리고 우리는 이미 아래의 포스팅에서
email : test@gmail.com
password: 1234
인 유저를 회원가입 시켜놨습니다.
그러면 다시 Postman을 켜봅시다. postman 사용법을 모르시는 분은 위의 포스팅을 참고해주시면 됩니다.
해당 사진과 같이 Post로 설정하고 주소를 http://127.0.0.1:3000/auth/login으로 설정한 후, Body부분을 raw와 Json으로 설정해줍시다.
그 후, { "email": "test@gmail.com", "password": "1234" } 를 적어서 Send 버튼을 누르면 사진 아래와 같이 acceessToken이 응답으로 날라옵니다. 이게바로 암호화된 JWT인 것입니다! 이것을 프론트엔드 개발자가 받아서 브라우저에 저장해두고, 서버에게 로그인이 필요한 요청을 보낼 때마다 HTTP Header(Authorization)에 jwt를 넣어서 보내면 됩니다.
로그인이 필요한 서비스
로그인이 필요한 서비스에 JwtAuthGuard를 등록해봅시다.
정말 간단한 api를 하나 만들겠습니다.
user.controller.ts 파일에 다음과 같이 코드를 작성해줍시다.
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
import { UserService } from './user.service';
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard';
@Controller('user')
export class UserController {
...
@UseGuards(JwtAuthGuard)
@Get('user-info')
async getUserInfo() {
return 'user-info Page';
}
}
@UseGuards로 JwtAuthGuard를 붙여줍니다.
/user/user-info 라는 url로 Get 요청을 날리면 JwtAuthGuard가 먼저 동작하여 로그인 여부를 파악할 것입니다.
그리고 로그인이 되어있다면, user-info Page를 리턴합니다.
그리고 npm run start:dev로 서버를 실행시켜봅시다.
그 후, Postman에서 다음과 같이 요청을 날려봅시다
로그인을 하지 않고 Get요청을 날렸기 때문에, 401 Unauthorized 오류가 뜨는 것을 볼 수 있습니다.
그렇다면 로그인을 하고 날려봅시다.
먼저 로그인을 해줍니다.
그러면 아래에 accessToken : value 형태로 데이터가 날라올텐데, value부분을 복사해줍시다. 따옴표 빼고요!
그리고 뒤의 사진처럼 Headers에 들어가서 Key에 Authorization, Value에 Bearer 복사한값 을 넣어줍시다. (Beare띄고 값 넣으면 됩니다)
그리고 GET, http://127.0.0.1:3000/user/user-info 를 하면, 위의 사진과 같이 아래 부분에 user-info Page라는 데이터를 받아볼 수 있습니다! 로그인 전에는 안됐는데 말이죠!
여기까지 로그인 포스팅을 마칩니다!
만약, 본인인지 체크를 하고 싶다면 req.user에서 유저id만 뽑아서 해당 유저id로 유저 정보를 select하는 DB로직을 추가해서 자신의 유저 정보만 보게할 수 있습니다.
아마, 이 글을 읽으시면서 다 따라했는데 오류가 발생하는 분들이 많을것이라고 생각합니다.
그렇다면, 둘 중 하나입니다.
1. 제 글을 읽으시면서 무언가를 빠뜨리셨거나
2. 제가 글을 작성하면서 무언가를 빼먹고 적은 경우
혹시 에러가 발생한다면 댓글에 남겨주시기 바랍니다.
참고로, 제 경험상 대부분의 에러는 오타나 네이밍실수로 인한 에러가 대부분이었기 때문에... 타자 하나하나 잘 보시면서 다시 이 글을 읽으면서 한 번 더 체크해보는걸 추천합니다.
자, 이제 Github에 업로드를 해봅시다.
로그인 로직이 정상적으로 실행이 된다면, Github에 업로드를 해봅시다.
다음 포스팅을 참고합시다.
참고하시라고 올려두는 사진
지금까지 긴 글 읽어주셔서 감사합니다.
'Back-end > 기초부터 따라하는 nest.js' 카테고리의 다른 글
#11. 기초부터 따라하는 Nest.js : Article CRUD (6) | 2023.05.11 |
---|---|
#10-1. 기초부터 따라하는 Nest.js : User Decorator (0) | 2023.05.11 |
#9. 기초부터 따라하는 Nest.js : 로그인의 원리와 Guard(1) - 개념편 (0) | 2023.04.17 |
#8. 기초부터 따라하는 Nest.js : TypeORM을 이용한 간단한 API작성 (6) | 2023.04.17 |
#7. 기초부터 따라하는 Nest.js : cross-env를 이용한 scripts에서의 환경변수 관리 (0) | 2023.04.02 |