express 는 자유로운 대신 사람마다 구조가 다양하다
nest는 정형화된 구조가 있다.
passport쓰는 이유
node생태계에서는 passport가 저명하다.
ORM 쓰면서 동시에 복잡하거나 성능상 문제가 있으면 쌩 쿼리로~
1:1 1:다 다:다: 테이블 마다 관계들 associate(db)로 정의 해준다.
ORM 쓸때도 그에 상응하는 SQL 도 알아야 잘 쓸수 있다. ( 씨퀄라이즈 보단 ORM이 쿼리에 가깝다)
트렌젝션이 이 뭔지 , join 여러번 안 할수 있는 경우 해결법
express 는 middleware?가 다다.
middleware : 요청과 응답을 조작해주는 역할.
nestjs Admin -> 간단한 CRUD 구축 해봐라.
https://nestjs-admin.com/
npm i -g @nestjs/cli
전역으로 nestjs/cli 설치
- 이런 경고 떴다.
npm WARN deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
해결책
이유
nest new fast(아무 프로젝트이름)
tsconfig.json 에서
package.json 에서
아무것도 안 하고 그냥
npm run start 해보면 -> nest start 로 치환돼서 실행됨.
에디터 마다 eslint,pretter ? 설정 해주는방법이 다르다.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const port = process.env.PORT || 3000;
await app.listen(port);
console.log(`listening on port ${port}`)
}
bootstrap();
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('abc')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('hello') // GET /abc/hello
getHello(): string {
return this.appService.getHello();
}
}
GET method 의 helllo 를 abc 와 연결하는 코드를 안 짯는데 Nest 가 우리 코드를 찾아서 연결해준다.
blackbox: 입려값을 넣으면 출력값이 어떻게 나오는지 모를경우
whitebox : 알고리즘 처럼 논리적으로 입력 값이 출력값이 나오는경우.
Controller 는 라우터랑 매칭이 된다고 생각.
얘 는 요청과 응답에 대해서는 모른다.
순수하게 동작만 하고 결과값만 컨트롤러한테 주고 컨트롤러는(req,res를 알아야 겠지) 그걸 응답해주고 또는 요청을 받아서 서비스에 넘기고
실질적 로직이 돌아가는부분.
트렌젝션 단위로 짜준다.
근데 왜 컨트롤러와 Services로 나눠 놓냐 ?
라우터 같은곳에서 한명의 유저 찾는게 중복 되는게 아니라 서비스에서만 유저 찾아서 여러번 되는게 중복 제거 된다?
req,res 를 모르기 때문에 독립적으로 쓸수있다.
npm i --save @nestjs/config
dotenv 를 nest 의 모듈로 만들어서 다시 또 연결 해야한다
그럴 바엔 그냥 nestjs/config 처럼 dotenv 같은거를 모듈로 감싼걸 가져다 쓰자.
app.modules.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [ConfigModule.forRoot()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
```` `
여기서 forRoot(), forFeature() 들은 거기 안에다 설정을 넣어줄 얘들을 그렇게 해준다 그게 아닐경우 그냥 아래처럼 AppContorller 이렇게만 .
.env.development / .env.production 같이 파일을 만들어서 환경변수 별로 불러오는기능.
근데 app.services.ts 에있는 return process.env.SECRET;
이부분에 직접 하는것보단 configServices 를 만들어서 하는게 좋다.
app.services.ts 에
constructor(private readonly configService: ConfigService){}
추가 해주고
app.modules.ts 에
@Module({
imports: [ConfigModule.forRoot({isGlobal:true})],
controllers: [AppController],
providers: [AppService,ConfigService],
})
.env 같은 환경변수들은 AWS비밀 저장소나 구글클라우드 플랫폼 보안 비밀 관리자에서 불러와서 process.env , 환경변수로 만드는 작업을 많이 한다 .
그러면 Config는 비동기 이다.( 외부에서 요청을 보내서 데이터를 가져와서 내 config로 만드닌까)
그럴땐
const getEnv = async () => {
const response = awiat axios.get('/외부서버로 부터 비밀키요청')
return {
response.data
}
}
@Module({
imports: [ConfigModule.forRoot({isGlobal:true, load:[getEnv]} )],
export class LoggerMiddleware implements NestMiddleware {
Class 'LoggerMiddleware' incorrectly implements interface 'NestMiddleware<any, any>'.
Property 'use' is missing in type 'LoggerMiddleware' but required in type 'NestMiddleware<any, any>'.ts(2420)
nest-middleware.interface.d.ts(7, 5): 'use' is declared here
use(request: Request, response: Response, next: NextFunction): void {
}
import { Injectable,Logger, NestMiddleware } from "@nestjs/common";
import { NextFunction, Request, Response } from "express";
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private logger = new Logger('HTTP')
use(request: Request, response: Response, next: NextFunction): void {
const{ ip, method, originalUrl } = request;
const userAgent = request.get('user-agent') || ''; // 요청의 헤더에서 가져오는것 없으면 빈문자열
response.on('finish', () => {
const {statusCode} = response;
const contentLength = response.get('content-length');
this.logger.log(
`${method}${originalUrl}${statusCode}${contentLength} - ${userAgent}${ip}`,
);
});
}
}
-실무에선 nest morgan패키지를 쓴다.
implements 문법은 왜 쓰나 ?
injectable() ?? DI dependency injection
그럼 굳이 providers는 왜 있냐 ?
func a(b,c)
return b+c;
a(1+2) = 3
a(2+2) = 4
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {
//원래는 생성자에 this.appService = AppService
}
@Get()
getHello() {
return this.appService.getHello();
//return new AppService().getHello();
}
여기서 new AppService() 을 생성해서 던져주는건.
func a(5,c)
return 5+c;
이렇게 해주는것이다 new로 생성해서 넣어주면 의존성이 없어진다.
나중에
new AppController(new AppService());
new AppController(new TestAppService());
이렇게 아래와 같이 테스트를 할때
AppService()가 생성자에 들어가서 this.appService가 된다.
그럼 TestAppService가 들어가면 이게 this.TestAppService가 된다.
constructor(private readonly TestAppService: AppService)
this.TestAppService 의 getHello() 를 불러온다.
근데 주의할게 websocket 같은경우에 의존성 주입 될때마다 웹소켓 서버가 생기는것이다.
그래서 프론트에서 채팅 하나 날렸는데 10번 서버에서 처리해야할수도있다 이럴때 글로벌로 선언해서 싱글톤 처럼 써줄수있다 ?
@Module({
imports: [ConfigModule.forRoot({isGlobal:true})],
controllers: [AppController],
//providers: [AppService,ConfigService],
providers : [
{
provide : AppService[고유한키 (클래스면 알아서 클래스의 이름을 고유한 키로 쓴다.)],
useClass: AppService,
//useValue:'123'
},
{
provide: 'CUSTOM_KEY',
useValue: 'CUSTOM_VALUE'
},
],
})
@Inject('CUSTOM_KEY') private readonly customValue,
import { Controller,Get,Post,Req,Res } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
getUsers(@Req() req) {
return req.user;
}
@Post()
postUsers() {
}
@Post('login')
logIn() {
}
@Post('logout')
logOut(@Req() req, @Res() res) {
req.logOut();
res.clearCookie('connect.sid', {httpOnly:true})
res.send('ok')
}
}
@Get()
getUsers(@Req() req) {
return req.user;
}
res.locals.jwt 와 같이 jwt 토큰으로 해도 res를 쓰는것
해결법 커스텀 데코리이터를 쓰는것 ! ~~
@Post()
postUsers(@Body() data: JoinRequestDto) {
}
요청에 Body에 들어있는 데이터가 data 에 담긴다.
API 설계 잘하려면 잘 만들어진 API를 많이 써봐라 .
공홈 openapi - introduction
npm install --save @nestjs/swagger
@ApiOperation({summary:'내 정보 조회'})
@Get()
getUsers(@Req() req) {
return req.user;
}
@ApiOperation({summary:'회원 가입'})
@Post()
postUsers(@Body() data: JoinRequestDto) {
this.usersService.postUsers(data.email, data.nickname, data.password)
return ;
}
@ApiOperation({summary:'로그인'})
@Post('login')
logIn() {
}
@ApiOperation({summary:'로그인'}) 이런식으로 해서 하면
localhost:3030/api 들어가면
@ApiProperty({
example : 'junyopar@gmail.com',
description: '이메일',
required: true,
})
public email: string;
@ApiProperty 로~
근데 swagger 는 쿼리스트링은 인식이 안된다. 직접 추가해주자
@ApiTags('DM')
@Controller('api/workspaces/:url/dms')
export class DmsController {
@ApiParam( {
name: 'url',
required: true,
description: '워크스페이스 url',
})
@ApiParam( {
name: 'id',
required: true,
description: '사용자 id',
})
@ApiQuery({
name: 'perPage',
required: true,
description: '한 번에 가져오는 개수',
})
@Get(':id/chats')
getChat(@Query() query, @Param() param) {
console.log(query.perPage, query.page);
console.log(param.id, param.url)
}
외부 dto 클래스 가져올땐 dto class 에 export class 라고 붙여야 가져올수 있다.
@ApiResponse({
status:500,
description:'서버에러',
})
@ApiResponse({
status:200,
description:'성공',
type: UserDto
})
@ApiOperation({summary:'내 정보 조회'})
@Get()
getUsers(@Req() req) {
return req.user;
}
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const User = createParamDecorator(
(data : unknown, ctx:ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
}
)
@ApiOperation({summary:'내 정보 조회'})
@Get()
getUsers(@Req() req) {
return req.user;
}
@ApiOperation({summary:'내 정보 조회'})
@Get()
getUsers(@User() user) {
return user;
}
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const Token = createParamDecorator(
(data : unknown, ctx:ExecutionContext) => {
const response = ctx.switchToHttp().getResponse();
return response.locals.jwt;
}
)
@Token() token
으로 사용 가능 이렇게 왜 Req, Res 를 사용하는걸 지양 해야하나?
나중에 테스트할때 종속 되는걸 막기 위해 .
softdelete 공부 // harddlete(row자체를 지우는거)
CRUD :
nest g resource
로 자동으로 만들어준다
npm i typorm pg
https://www.npmjs.com/package/typeorm
--save ( package.json 에 저장해 주는 옵션) 옵션이 이제 디폴트로 들어가서 따로 설정 안 해줘도 된다.