다음으로 jwt(jason web token) 구현을 위한 준비입니다. import jwtConstants와 관련된 부분이에요.
auth폴더 아래에 'constants.ts' 파일을 추가하여 아래와 같이 구현합니다. jwt를 생성할 때 사용하는 시크릿 키로 외부에 노출되면 안 되는 부분입니다.
export const jwtConstants = {
secret: 'secretKey', // token 발급 시 사용되는 시크릿 키. 노출되면 안됨.
};
마지막으로 JwtStrategy를 위해 'jwt.strategy'를 생성해줍니다. LocalStrategy처럼 JWT를 위한 PassportStrategy확장형 JwtStrategy입니다.
local에서는 매핑할 filed 정도만 정의해 주었는데 jwt에서는 여러 가지 정보가 있어요. 상세 내용은 주석을 참고하세요.
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // Request에서 JWT를 추출하는 방법 중 Bearer Token 사용
ignoreExpiration: false, // jwt 보증을 passport 모듈에 위임함. 만료된 JWT인경우 request거부, 401 response
secretOrKey: jwtConstants.secret, // token 발급에 사용할 시크릿 키
});
}
async validate(payload: any) {
return { userId: payload.sub, userEmail: payload.userEmail };
}
}
여기까지 import 된 부분에 대한 설명이었고, auth.module의 imports를 보면 UsersModule, PassportModule을 가져왔고 JwtModule은 register를 통해 등록하였어요. register() 안쪽 부분이 JwtModule에 대한 Options이에요.
auth module까지 끝났고요. 이제 guards에 대한 부분을 구현해 보도록 하겠습니다.
Guards의 역할은 request가 처리될지 말지를 결정합니다. 예를 들어 로그인에 정상적으로 성공한 경우 이후의 프로세스를 실행할 수 있지만, 로그인에 실패한 경우 더 이상 접근할 수 없도록 하는 것이지요.
'app.controllers.ts'를 열어 수정해줍니다. app.으로 왔다니 뭔가 끝이 보이는 거 같네요.
import { Controller, Request, Get, Post, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiParam } from '@nestjs/swagger';
import { AuthGuard } from '@nestjs/passport';
import { get } from 'http';
import { AppService } from './app.service';
import { AuthService } from './auth/auth.service';
import { JwtAuthGuard } from './auth/jwt-auth.guard';
import { LocalAuthGuard } from './auth/local-auth.guard';
@Controller('auth')
export class AppController {
constructor(
private authService: AuthService,
private appService: AppService,
) {}
// @UseGuards(AuthGuard('local'))
@UseGuards(LocalAuthGuard)
@Post('/login')
async login(@Request() req) {
// return req.user;
return this.authService.login(req.user);
}
@UseGuards(JwtAuthGuard)
@Get('/profile')
getProfile(@Request() req) {
return req.user;
}
@Get('/helloworld')
getHello(): string {
return this.appService.getHello();
}
@Get('/health')
@ApiOperation({
summary: 'auth health check',
description: 'auth application 이 정상 상태인지 체크한다.',
})
getHealth(): string {
return this.appService.getHealth();
}
}
guard에 대한 부분도 strategy와 같이 local, jwt가 있어요.
먼저 'local-auth.guard.ts'를 구현합니다.
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
'jwt-auth.guard.ts'
import {
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
// Add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
handleRequest(err, user, info) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
nest cli는 많은 기능을 제공하는 데, 그중 generator를 이용하여 CRUD를 구현해 보겠습니다.
먼저 nest -h 명령어를 통해 nest에서 사용하는 alias를 확인해 볼게요.
nest -h
Usage: nest <command> [options]
Options:
-v, --version Output the current version.
-h, --help Output usage information.
Commands:
new|n [options] [name] Generate Nest application.
build [options] [app] Build Nest application.
start [options] [app] Run Nest application.
info|i Display Nest project details.
update|u [options] Update Nest dependencies.
add [options] <library> Adds support for an external library to your project.
generate|g [options] <schematic> [name] [path] Generate a Nest element.
Schematics available on @nestjs/schematics collection:
┌───────────────┬─────────────┬──────────────────────────────────────────────┐
│ name │ alias │ description │
│ application │ application │ Generate a new application workspace │
│ class │ cl │ Generate a new class │
│ configuration │ config │ Generate a CLI configuration file │
│ controller │ co │ Generate a controller declaration │
│ decorator │ d │ Generate a custom decorator │
│ filter │ f │ Generate a filter declaration │
│ gateway │ ga │ Generate a gateway declaration │
│ guard │ gu │ Generate a guard declaration │
│ interceptor │ in │ Generate an interceptor declaration │
│ interface │ interface │ Generate an interface │
│ middleware │ mi │ Generate a middleware declaration │
│ module │ mo │ Generate a module declaration │
│ pipe │ pi │ Generate a pipe declaration │
│ provider │ pr │ Generate a provider declaration │
│ resolver │ r │ Generate a GraphQL resolver declaration │
│ service │ s │ Generate a service declaration │
│ library │ lib │ Generate a new library within a monorepo │
│ sub-app │ app │ Generate a new application within a monorepo │
│ resource │ res │ Generate a new CRUD resource │
└───────────────┴─────────────┴──────────────────────────────────────────────┘
맨 아래에 보시면 CRUD가 있습니다. resource 또는 res를 이용하면 CRUD에 필요한 템플릿을 만들 수 있습니다.
저는 auth라는 CRUD 기능을 만들어 보겠습니다.
nest g res auth
? What transport layer do you use?
> REST API
GraphQL (code first)
GraphQL (schema first)
Microservice (non-HTTP)
WebSockets
? Would you like to generate CRUD entry points? (Y/n) Y
CREATE src/auth/auth.controller.spec.ts (556 bytes)
CREATE src/auth/auth.controller.ts (883 bytes)
CREATE src/auth/auth.module.ts (240 bytes)
CREATE src/auth/auth.service.spec.ts (446 bytes)
CREATE src/auth/auth.service.ts (607 bytes)
CREATE src/auth/dto/create-auth.dto.ts (30 bytes)
CREATE src/auth/dto/update-auth.dto.ts (164 bytes)
CREATE src/auth/entities/auth.entity.ts (21 bytes)
UPDATE src/app.module.ts (388 bytes)
nest g res 명령 실행 시 어떤 전송방법을 사용할지, CRUD point를 생성할지에 대해 물어봅니다.
저는 REST API, Y를 선택하여 생성하였습니다. 이제 start를 해보겠습니다.
yarn start
"Nest application successfully started" 성공적으로 시작했다고 합니다.
"http://localhost:3000/auth"로 접속해 보니 "Hello" 메시지를 정상적으로 만나볼 수 있습니다.
하지만 우리는 이걸 목적으로 한 것이 아니기에 "http://localhost:3000/auth/1"로 접속해 봅니다.
이렇게 나오게 되면, Resource생성이 정상적으로 완료되었음을 확인할 수 있습니다.
생성된 폴더 구조를 살펴보도록 하겠습니다.
.ts파일 중 spec.ts는 테스트 파일, dto, entities폴더에 있는 파일은 DB와 관련된 파일입니다.
먼저 이외의 controller, moduel, service에 대해서 살펴보겠습니다.
> service
service파일은 비즈니스 로직을 처리하는 파일입니다. auth.service.ts의 AuthService를 살펴보면
create, findAll, findOne, update, remove라는 메서드가 있습니다.
방금 전, "http://localhost:3000/auth/1"를 호출하였을 때 만났던 메시지가 findOne 메서드를 통해 response 되었겠네요.
localhost의 root경로로 request가 response 되는 것을 확인한 것입니다.
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
app.controller.ts를 보면 Spring 프레임워크와 굉장히 익숙한 구조임을 알 수 있어요. 서버가 수행해야 할 일을 '@' Annotation, Decorator(어노테이션, 데코레이터)로 기술합니다.
@Controller 데코레이터를 클래스에 선언함으로써 해당 클래스는 컨트롤러의 역할을,
@Get() 데코레이터를 가진 getHello 함수는 root경로로 들어오는 request(요청)를 처리할 수 있습니다.
@Controller() 데코레이터를 보면 ( )가 있다. 여기에 인자를 입력하여 라우팅 경로는 정해줄 수 있어요. 예를들어 backend에서 주로 사용하는 'app'이라고 입력하면 우리가 지난번에 테스트했던 https://localhost:3000/app으로 접근해야 "hello world"를 만날 수 있는 것이죠!
@Get() 안에는 '/'가 생략되어 루트 경로이며, '/helloworld'로 변경한다면, 기존의 http://localhost:3000/ 요청에 대하여 404 에러를 만나게 될 것이고, http://localhost:3000/helloworld로 요청해야 "hello world"페이지를 응답받을 수 있을 것이다.
소스로 보면 @Controller(에는 "prefix:", @Get(에는 "path"라고 붙여진 게 확실히 보여서 더 쉽게 이해할 수 있습니다.
중간에 어떤 package manager를 사용할 것인지 물어본다. 필자는 yarn을 선택하였다.
? Which package manager would you ❤️ to use? yarn
✔ Installation in progress... ☕
🚀 Successfully created project bcheck-auth-nest
👉 Get started with the following commands:
$ cd [project name]
$ yarn run start
Thanks for installing Nest 🙏
Please consider donating to our open collective
to help us maintain this package.
🍷 Donate: https://opencollective.com/nest
위와 같이 뜨면 프로젝트 생성이 완료된 것이다. 생성된 프로젝트를 개발 툴로 열어 구조를 살펴보자.
참고로 글쓴이는 IntelliJ를 이용한다.
[Project Name] - [src] 아래에 app.으로 시작하는 파일들과 main.ts 파일이 생성되었음을 확인할 수 있다.
각각의 역할은 아래와 같다.
app.controller.spec.ts
컨트롤러 단위 테스트용
app.controller.ts
기본 컨트롤러
app.module.ts
application의 root module
app.service.ts
단일 메서드를 사용하는 기본 서비스
main.ts
NestFactory를 사용하여 Nest application 인스턴스를 작성하는 엔트리 파일
3. main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
Nest application은 인스턴스를 생성하기 위해 NestFactory를 사용한다. NestFactory는 application 응용프로그램 인스턴스를 만들 수 있는 정적 메서드를 제공하며, careate() 메서드는 INestApplication (인터페이스 응용프로그램 객체)을 반환한다.
Spring 개발 유경험자로 Node를 처음 접하며 Study하는 내용을 기록해보고자 한다.
1. NestJS는?
NestJS는 Node.js 기반의 웹 API 프레임워크이다. 들어만 본 Express를 사용하고, Node.js의 자체 특성인 사용하기 쉽고, 확장성이 뛰어나다. 이로 인해 품질이 일정치 않고, 적합한 라이브러리를 찾기 위해 사용자의 수고가 필요하다는 단점이 있는데 이를 보완하는 것이 NestJS이다.
Angular의 영향을 많이 받은 NestJS는 DI(Dependency Injection, 의존성 주입)이라는 객체지향 개념을 도입한 부분에서 Spring과 공통점이 있다. 소스를 보아도 스프링에서 Anotation이라고 불리는 '@'가 보인다.