Custom Log

 

src/middleware/logger/logger.service.ts

import { ConsoleLogger, Injectable } from "@nestjs/common";

@Injectable()
export class LoggerService extends ConsoleLogger {

    constructor() {
        super()
        this.setLogLevels(['verbose', 'debug', 'log', 'warn', 'error'])
    }

    appLog(message: string) {
        this.log(message)
    }

    errorLog(message: string) {
        this.error(message);
    }

}

 

src/middleware/logger/logger.module.ts

import { Module } from "@nestjs/common";
import { LoggerService } from "./logger.service";

@Module({
    providers: [LoggerService],
    exports: [LoggerService]
})
export class LoggerModule {}

src/api/api.module.ts  →  imports: [LoggerModule]

import { Module } from '@nestjs/common';
import { ApiController } from './api.controller';
import { ApiService } from './api.service';
import { LoggerModule } from '@src/middleware/logger/logger.module';

@Module({
  imports: [LoggerModule],
  controllers: [ApiController],
  providers: [ApiService]
})
export class ApiModule {}

 

src/api/api.controller.ts

import { Controller, Get } from '@nestjs/common';
import { LoggerService } from '@src/middleware/logger/logger.service';

@Controller('api')
export class ApiController {

    constructor(private loggerService: LoggerService) {
        loggerService.setContext(ApiController.name)
    }

    @Get('app')
    app() {
        this.loggerService.appLog('application log')
    }

    @Get('error')
    error() {
        this.loggerService.errorLog('error log')
    }

}

API 호출
console log 확인 완료

 


변수로 선언해서 사용

 

main.ts

const logger = new LoggerService();
logger.setContext('main.ts')


async function bootstrap() {

	...
    logger.appLog(`load main.ts`)
	...

}
bootstrap();

 


Middleware Log

미들웨어를 통해 API Request 요청 로그를 남겨보자

 

src/middleware/logger/logger.express.ts

import { Injectable, NestMiddleware } from "@nestjs/common";
import { LoggerService } from "./logger.service";
import { NextFunction } from "express";

@Injectable()
export class LoggerExpress implements NestMiddleware {

    constructor(private loggerService: LoggerService) {
        this.loggerService.setContext(LoggerExpress.name)
    }

    use(req: Request, res: Response, next: NextFunction) {
        this.loggerService.verbose(`${req.url} [${req.method}] ${JSON.stringify(req.body)}`);
        next();
    }

}

 

src/middleware/logger/logger.fastify.ts

import { Injectable, NestMiddleware } from "@nestjs/common";
import { LoggerService } from "./logger.service";
import { FastifyReply, FastifyRequest } from "fastify";

// $ npm i fastify
@Injectable()
export class LoggerFastify implements NestMiddleware {

    constructor(private loggerService: LoggerService) {
        this.loggerService.setContext(LoggerFastify.name)
    }

    use(req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void) {
        this.loggerService.warn(`${req.url} [${req.method}]`);
        next()
    }

}

 

src/app.module.ts

import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApiModule } from './api/api.module';
import { LoggerFastify } from './middleware/logger/logger.fastify';
import { LoggerModule } from './middleware/logger/logger.module';
import { LoggerExpress } from './middleware/logger/logger.express';

@Module({
  imports: [ApiModule, LoggerModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
/*
    path: '*'
    method: RequestMethod. ALL, GET, HEAD 등
*/
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerExpress) // 또는 LoggerFastify
      .forRoutes({ path: '/api/*', method: RequestMethod.ALL })
  }

}

API 요청하기
console log 확인

 


참고 링크

https://docs.nestjs.com/techniques/logger

https://docs.nestjs.com/techniques/performance

 

Nest 프로젝트의 package.json - scripts의 테스트 명령어를 보면 Jest 테스트 라이브러리를 사용한다

테스팅 파일명에는 .spec 또는 .test 의 접미사가 있어야 한다

 

describe, it, test, expect

describe() 테스트 모듈을 그룹화하거나 계층별로 나눌 수 있고, it(), test(), expect()**** 키워드를 통해 테스트 코드를 작성한다

it()test() 키워드의 alias이며, 대체하여도 동일하게 작동한다

 

 

beforeAll, afterAll, beforeEach, afterEach

beforeAll(), afterAll() 는 테스트 파일의 시작과 끝에 한번 실행된다. 전역 변수를 관리할 수 있다

beforeEach(), afterEach() 는 테스트 파일 내 테스트 케이스의 시작과 끝에 실행된다

 

 

실행 순서

beforeAll → 
	[beforeEach → (describe - *it) → afterEach] → 
    	・・・ 
    →  [beforeEach → (describe - *it) → afterEach] 
→ afterAll

메서드 compile() 는 비동기식이므로 대기해야 합니다. 모듈이 컴파일되면 메서드를 사용하여 모듈이 선언하는 모든 정적 인스턴스(컨트롤러 및 공급자)를 검색할 수 있습니다

 

 

 

 

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

단위 (Unit) 테스트

 

api.service.ts

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

@Injectable()
export class ApiService {

    /*
        get
    */
    getOne(id: number) {
        return id
    }

    getEmptyArr() {
        return []
    }

    getPlusOne(num: number) {
        return num + 1;
    }

    /*
        update
    */
    update(b: boolean) {
        return !b;
    }

}

 

api.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { ApiService } from './api.service';
import { describe } from 'node:test';

describe('ApiService', () => {
  let service: ApiService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [ApiService],
    }).compile();

    service = module.get<ApiService>(ApiService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('get', () => {
    it('should return id', () => {
      const id = 1;
      const ret = service.getOne(id);

      expect(ret).toEqual(id);
    })

    it('should return empty array', () => {
      const ret = service.getEmptyArr();

      expect(ret).toEqual([]);
    })
  })

  describe('update', () => {
    it('should return falsy', () => {
      const b = true;
      const ret = service.update(b);

      expect(ret).toBeFalsy();
    })
  })

});

 

테스트

$ npm run test

 

$ npm run test:cov

 

api.service.ts % Funcs 수치를 보면 75라고 나와있다. service.ts 파일의 함수는 총 4개인데, service.spec.ts에서는 3개의 함수의 테스팅 코드만 작성하였다

 

 

나머지 함수의 테스트 코드도 작성한 후, 실행하면 %Funcs 수치가 100으로 나온다

 


통합 (e2e, End to End) 테스트


npm run test:e2e 명령어를 통해 통합 테스트를 진행할 수 있다.

 

main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe(
      {
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true
      }
    )
  )
  await app.listen(3000);
}
bootstrap();

 

test/app.e2e-spec.ts

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();

    app.useGlobalPipes(
      new ValidationPipe(
        {
          whitelist: true,
          forbidNonWhitelisted: true,
          transform: true
        }
      )
    )

    await app.init();
  });

 

main.ts에 app에 대한 설정 (ex.GlobalPipes)을 했다면, app.e2e-spec.ts 파일에도 동일하게 설정해 주어야 한다

 

 


참고 링크

https://docs.nestjs.com/fundamentals/testing

https://nomadcoders.co/nestjs-fundamentals

https://jestjs.io/docs/api

'JavaScript > NestJS' 카테고리의 다른 글

[NestJS] 환경 변수 (config) 사용하기  (0) 2023.08.20
[NestJS] Logging 하기  (0) 2023.08.19
[NestJS] 경로 alias 설정하기  (0) 2023.08.17
[NestJS] API 생성하기 (+ CORS)  (0) 2023.08.17
[NestJS] 시작하기  (0) 2023.08.17
import myCode from 'src/folder1/folder2/folder3/folder4/folder5/index

간단한 프로젝트는 상관 없지만, 프로젝트 규모가 커지면 코드에 표현되는 경로가 깔끔하지 않을 수 있음

절대 경로(alias)를 설정

 


첫번째 방법

tsconfig.json

{
  "compilerOptions": {
    ...
    "baseUrl": "./",
    ...
    "paths": {
      "@src/*": [
        "./src/*"
      ],
      "@src/api/*": [
        "./src/api/*"
      ]
    }
  }
}

두번째 방법

tsconfig.paths.json

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@src/*": ["./src/*"],
      "@app/*": ["./src/app/*"],
      "@loader/*": ["./src/loader/*"],
      "@middleware/*": ["./src/middleware/*"]
    }
  }
}

tsconfig.json

{
  "compilerOptions": {
    ...
  },
  "extends": "./tsconfig.paths.json"
}

경로 적용 확인

app.modules.ts 파일 변경

변경 전
import { ApiModule } from './api/api.module';

변경 후
import { ApiModule } from '@src/api/api.module';

혹시 watch 모드로 실행되고 있다면 적용되지 않을 수 있다. 서버를 재실행하여 다시 확인해 보자

 


Jest 테스트를 위한 경로 설정

tsconfig.json 파일 설정으로는 *spec.ts 파일의 경로 alias가 적용이 되지 않는다

package.json

{
    ...
  "jest": {
    ...
    "moduleNameMapper": {
      "^@src/(.*)$": "<rootDir>/$1"
    }
  }
}

jest 테스트 경로 alias가 적용되었다

'JavaScript > NestJS' 카테고리의 다른 글

[NestJS] 환경 변수 (config) 사용하기  (0) 2023.08.20
[NestJS] Logging 하기  (0) 2023.08.19
[NestJS] Jest 테스트하기  (0) 2023.08.17
[NestJS] API 생성하기 (+ CORS)  (0) 2023.08.17
[NestJS] 시작하기  (0) 2023.08.17


NestJS는 강력한 CLI (Command-Line Interface)를 지원한다.

CLI 를 통해 API를 만들기 위한 모듈(module, mo), 컨트롤러(controller, co), 서비스(service, s)를 생성해보자


# CLI 통한 생성 방법
$ nest (generate|g) (name|alias) name


import { Controller, Get, Head } from '@nestjs/common';

@Controller('api')
export class ApiController {

    @Head()
    foo() {}

    @Get()
    get(): string {
        return 'hello World!'
    }

}

데코레이터(@)를 선언하여, API의 경로와 메서드를 정의할 수 있다.
* HEAD 메서드를 사용할 때, 동일한 경로의 GET 메서드가 존재한다면, 반드시 HEAD 메서드를 먼저 선언해야 한다
데코레이터는 중요하지만, 함수명은 의미가 없다. (하지만 함수명을 마음대로 짓자는 뜻은 아니다, 클린 코드


Express 모듈 사용하기

import { Controller, Get, Req, Request, Res, Response } from '@nestjs/common';

@Controller('api')
export class ApiController {

    @Get('express1')
    get1(@Request() request, @Response() response) {
        response.status(200).send(`${request.method} ${request.url} ${request.ip}`)
    }

    @Get('express2')
    get2(@Req() req, @Res() res) {
        res.status(200).json({
            method: req.method,
            url: req.url,
            ip: req.ip
        })
    }

}


Query, Param, Body 사용하기

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

@Controller('api')
export class ApiController {

    @Get('param/:id')
    getParam(@Param('id') id) {
        return `id: ${id}`
    }

    @Get('params/:id/:name')
    getParams(@Param('id') id, @Param('name') name) {
        return `id: ${id}, name: ${name}`
    }

    @Get('query')
    getQuery(@Query('id') id) {
        return `id: ${id}`
    }

    @Post('body')
    postBody(@Body() body) {
        return body
    }

}


Put, Patch, Delete, HttpCode, Redirect

import { Body, Controller, Delete, Get, HttpCode, Patch, Put, Redirect } from '@nestjs/common';

@Controller('api')
export class ApiController {

    @Put()
    put(@Body() body) { }

    @Patch()
    patch() { }

    @Delete()
    @HttpCode(204)
    delete() { }

    @Get('redirect')
    @Redirect('https://nestjs.com', 301)
    getRedirect() {}

}

CORS  설정

 

첫번째 방법

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.enableCors({ origin: "*", methods: "GET" })

  await app.listen(3000);
}
bootstrap();

 

두번째 방법

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    cors: {
      origin: ["*"]
      , optionsSuccessStatus: 200
      , maxAge: 300
      , credentials: true
    }
  });

  await app.listen(3000);
}
bootstrap();

 


참고 링크

NestJS Documentation - Custom decorators

https://docs.nestjs.com/controllers

 

'JavaScript > NestJS' 카테고리의 다른 글

[NestJS] 환경 변수 (config) 사용하기  (0) 2023.08.20
[NestJS] Logging 하기  (0) 2023.08.19
[NestJS] Jest 테스트하기  (0) 2023.08.17
[NestJS] 경로 alias 설정하기  (0) 2023.08.17
[NestJS] 시작하기  (0) 2023.08.17

Nest makes use of robust HTTP Server frameworks like Express (the default) and optionally can be configured to use Fastify as well!

  • Nest(NestJS)는 효율적이고 확장 가능한 Node.js 서버 (백엔드) 애플리케이션을 구축하기 위한 프레임워크
    • TypeScript, JavaScript 지원
    • HTTP 프레임워크를 활용하여 구성
      • Express: JavaScript로 작성되고 Node.js 런타임 환경에서 구동되는 인기 있는 웹 프레임워크
      • Fastify: 최대 효율성과 속도를 제공하는 데 중점을 둔 고성능 및 낮은 오버헤드 프레임워크
    • 프로그래밍 요소 결합
      • OOP (Object Oriented Programming) : 객체 지향 프로그래밍
      • FP (Functional Programming) : 함수형 프로그래밍
      • FRP (Functional Reactive Programming) : 함수 반응형 프로그래밍, 비동기 데이터 스트림 이용
    • CLI (Command-Line Interface) 지원

 

 

설치, 프로젝트 생성

# i: install , g: global , n: new
$ npm i -g @nestjs/cli
$ nest (n|new) <project_name>
$ npm install

Node 버전 16 이상에서만 NestJS 설치 가능, "node -v" 명령어로 버전 확인!
프로젝트 my-project 생성

 

프로젝트 기본 구조

동작 과정은 main.js → app.module.ts → app.controller.ts → app.service.ts로 진행


어플리케이션의 시작점&nbsp; main.ts

 

NestFactory.create( )  매개변수에 프로젝트에서 사용할 루트 모듈이 들어간다. 기본 구조에서는 app.module.ts 이 들어가있지만 커스텀 모듈을 지정할 수 있다
* 메인 함수명이 bootstrap 일 필요는 없다 (함수명 변경 가능)


Module (모듈)

Provider와 Controller들을 논리적인 기능이나 도메인에 따라 하나로 묶어주는 역할을 하며, 재사용성을 높여줌

AppModule 에서는 데코레이터(@, Decorator) 를 사용해 컨트롤러와 프로바이더(서비스)를 명시

이유는  제어의 역전(IoC, Inversion of Control) 기술 중 하나인 의존성 주입(DI, Defendency Injection)을 하기 위함


Controller (컨트롤러)

외부와의 통신, 라우팅을 담당

constructor (생성자) 매개변수 선언을 통해 AppModule에서 선언한 의존성 주입을 사용할 수 있음


Provider (공급자)

Nest에서 가장 간단하고 작은 단위의 컴포넌트를 표현
비즈니스 로직
기본적으로 @Injectable() 데코레이터로 나타내며,  다른 프로바이더나 컨트롤러에 주입(Inject)될 수 있음
종류로는 가장 간단한 Service부터 Middleware, Pipe, Guard, Interceptor 등이 존재

*spec.ts 파일들은 테스트를 하기 위한 파일이다.

test 폴더에 있는 app.e2e-spec.ts 파일을 통합 테스트를 하기 위함

src 아래 위치에 속해있는 다른 spec.ts 파일은 유닛 테스트를 하기 위한 파일들이다.

 

package.json의 scripts 확인하여 테스트를 진행

$ npm run test
$ npm run test:watch
$ npm run test:cov
$ npm run test:debug
$ npm run test:e2e

Mac의 터미널(Terminal)을 통해 진행하다 보면 권한(permission) 문제 때문에, 막혀서 sudo 명령어를 계속 입력해 줘야 할 수 있다.
공부를 하기 위해서 chmod 명령어를 통해 권한을 열어주도록 하자

# '~'는 사용자의 폴더 경로를 뜻한다. (ex. /Users/sejin )
# 프로젝트에서 모듈 설치 시 (ex. npm install)
$ chmod -R 755 ~/.npm

# my-project는 내가 생성한 nest 프로젝트 폴더
# 프로젝트 실행 시 (ex. npm run start:dev)
$ chmod -R 755 my-project

참고 링크

NestJS Documentation
Nomadcoders Nest

'JavaScript > NestJS' 카테고리의 다른 글

[NestJS] 환경 변수 (config) 사용하기  (0) 2023.08.20
[NestJS] Logging 하기  (0) 2023.08.19
[NestJS] Jest 테스트하기  (0) 2023.08.17
[NestJS] 경로 alias 설정하기  (0) 2023.08.17
[NestJS] API 생성하기 (+ CORS)  (0) 2023.08.17

PM2 (Process Manager)는 JavaScript 런타임 Node.js의 프로세스 관리자이다.

설치

# Install
npm install pm2@latest -g
yarn global add pm2
# Update
pm2 update

 

어플리케이션 실행

pm2 start app.js

 

프로세스 확인

pm2 [list|ls|status]

 

로그 모니터링

# To display logs in realtime
pm2 logs
# Here is a realtime dashboard that fits directly into your terminal
pm2 monit
# Web based dashboard, cross servers with diagnostic system
pm2 plus

pm2 monit

 

설정 파일

# You can also create a configuration file, called Ecosystem File, 
# to manage multiple applications.
pm2 ecosystem
# And start it easily
pm2 start ecosystem.config.js
// ecosystem.config.js
module.export = {
     apps: [{
      name: 'app_name',
      script: './app.js',
      instances: 0,
      exec_mode: 'cluster'
    }]
}

instances 값을 0으로 설정하면 CPU 코어 수 만큼 프로세스를 생성하겠다는 뜻이다.

 

프로세스 개수 늘리기

# 프로세스 4개 추가
pm2 scale app_name +4 
# 프로세스 4개 제거
pm2 scale app_name 4

 

프로세스 관리

pm2 stop <app_name> # 또는 파일 경로 (ex. /home/server.js)
pm2 delete <app_name>
pm2 restart <app_name>
pm2 reload <app_name>
pm2 show <app_name>

 

 


 

참고 사이트

오류

C:\Users\tpwls\vscode> npx create-next-app@latest
npm ERR! syscall lstat
npm ERR! path C:\Users\tpwls\AppData\Roaming\npm
npm ERR! enoent ENOENT: no such file or directory, lstat 'C:\Users\tpwls\AppData\Roaming\npm'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent 

npm ERR! A complete log of this run can be found in: C:\Users\tpwls\AppData\Local\npm-cache\_logs\2023-07-28T15_22_52_433Z-debug-0.log

해결 방법

C:\Users\tpwls\vscode> npm i -g npx
PayloadTooLargeError: request entity too large

413 Payload Too Large

Body 요청 시, JSON 길이가 길면  클라이언트는 413 코드 응답을 받게되고, 서버에서는 PayloadTooLargeError 가 발생한다.

 

app.use(express.json({
  limit: "50mb"
}))

위 코드를 추가하여 해결

간단한 서버 실행하기

https://github.com/oven-sh/bun

 

GitHub - oven-sh/bun: Incredibly fast JavaScript runtime, bundler, transpiler and package manager – all in one.

Incredibly fast JavaScript runtime, bundler, transpiler and package manager – all in one. - GitHub - oven-sh/bun: Incredibly fast JavaScript runtime, bundler, transpiler and package manager – all i...

github.com

// http.ts
export default {
  port: 3000,
  fetch(request: Request) {
    return new Response("Hello World");
  },
};

// bun ./http.ts

bun 문서에서 제공하는 기본 예제 코드입니다.

서버를 실행하기 전에 ifconfig 명령어를 통해 wsl의 네트워크 IP 주소를 확인하세요
윈도우 브라우저에서 WSL 서버로 접속 확인

 

리액트 서버 실행하기

bun create react ./app
cd app
bun dev

React 프로젝트를 생성하고, 프로젝트 폴더로 이동하여 bun 명령어를 통해 서버를 실행
Welcome to React!

 


https://bun.sh/

 

Bun is a fast all-in-one JavaScript runtime

Bundle, transpile, install and run JavaScript & TypeScript projects – all in Bun. Bun is a new JavaScript runtime with a native bundler, transpiler, task runner and npm client built-in.

bun.sh

 

https://www.youtube.com/watch?v=t9924eteb-4 

 

https://code.visualstudio.com/

현재 사용하시는 윈도우에 VSCode가 설치되어 있어야 합니다.

Extensions (Ctrl + Shift + X)에서 Remote - WSL 를 설치합니다
상단 File - Open Folder 를 누르시고, bun 패키지를 설치했던 사용자의 홈 디렉토리로 이동합니다
터미널에서 mkdir 명령어로 폴더/파일 생성 가능하지만, 버튼을 통해서 생성할 경우 Permission denied 문제가 발생했습니다
chown -R <사용자 계정> <경로> 를 통해 권한을 부여하여 해결했습니다
처음 권한 문제로 찾다가 검색했던 내용인데, 위 명령어로도 불가능하다면 이 설정 참고해주세요. Settings ( Ctrl + , )에서 wsl 검색 -> Polling 체크

 

 

환경 구축 과정에서 생략된 부분이 있을 수 있습니다. 댓글 남겨주시면 답글 남기도록 하겠습니다!

+ Recent posts