NestJS를 위한 express 핵심 원리#1

아직·2022년 11월 14일
0
post-thumbnail

1)

{
  "scripts": {
    "build": "tsc",
    "start:dev": "tsc-watch --onSuccess \"node dist/app.js\"",
    "prestart": "npm run build",
    "start": "node dist/app.js"
  },
  "devDependencies": {
    "@types/node": "^15.3.0",
    "prettier": "^2.2.1",
    "tsc": "^2.0.3",
    "tsc-watch": "^4.2.9",
    "typescript": "^4.3.4"
  }
}

터미널에서 npm install 시 추적하는 "devDependencies"와 npm start 시 추적하는 "scripts"를 포함하는 package.json 파일의 내용이다. "scripts" 부분을 좀 더 주목할 필요가 있는데, npm strat:dev는 파일을 바로 실행하지 않고 npm prestart를 먼저 실행한다. 이 때, run build 부분에서 "build"에 정의되어 있는 ts compiler를 실행하게 되면 tsc-watch 조건에 따라 성공적인 watch 후 /dist 경로에 생성되는 app.js 파일이 실행된다.

ts compile 후 js 파일을 생성하는 경로 등의 옵션은 package.json과 같은 층위의 tsconfig.json 파일에 정의되어 있다.

https://www.staging-typescript.org/tsconfig 참조

2)

  "devDependencies": {
    "@types/express": "^4.17.14",
    "@types/node": "^15.3.0",
    "prettier": "^2.2.1",
    "tsc": "^2.0.3",
    "tsc-watch": "^4.2.9",
    "typescript": "^4.3.4"
  },
  "dependencies": {
    "express": "^4.18.2"
  }

경로에서 npm i express를 실행하면 package.json 파일에 "dependencies" 부분이 코딩되는 것을 확인할 수 있다. "devDependencies"의 차이점은 후자는 개발할 때 필요한 라이브러리들을 때려 넣는 느낌이라면, 전자는 production-level을 위해서 분리한 것이라고 보면 된다.

npm i @types/express -D

어차피 node dist/app.js를 실행할 것이므로, 개발에 필요한 typescript는 devDependencies 조건으로 설치했다.

3)

const app: express.Express = express();

서버 역할을 하는 app은 express의 instance이다. 반대로 말하면, express에는 constructor가 있음을 예상해볼 수 있다.

4)

app.use((req, res, next) => {
  console.log(req.rawHeaders[1]);
  console.log('this is logging middleware');
  next();
});

app.get('/cats/som', (req, res, next) => {
  console.log('this is som middleware');
  next();
});
...
app.get('/cats/som', (req, res) => {
  res.send({ som: Cat[1] });
});

app.ts 파일에서 전체적으로 사용되는 middleware는 app.use를 사용한 반면, 특정 endpoint를 위한 middleware는 app.get와 next()의 조합으로 res.send()가 있는 router에 연결되었다.

next()가 단순히 "동작을 끝내지 않고 넘김"의 의미를 넘어서, "res에 관련된(혹은 router를 종료하는) 특정 동작을 수행하기 전까지는 endpoint에 관한 정보를 살린채로 가져감"이라는 의미까지 갖는다고 생각할 수 있을 것 같다.

'endpoint에 관한 정보'라는 것이 아무래도 req에 담겨있을 것 같은데 더 확인해 볼 필요는 있다.

5)

router.get('/cats', (req, res) => {
  try {
    const cats = Cat;
    // throw new Error('db connect error');
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
});

try/catch 구조로만 끝나지 않고 res.send에서 성공한 경우에는 data를 실어주고 실패한 경우에는 error를 실어주도록 나뉜 구조가 마음에 든다.

6)

app.use(express.json());

express는 req.body에 실린 json 타입의 정보를 읽지 못한다고 한다.

json() method는 express module의 body-parser property?에 정의되어 있는데, 이 자체가 middleware가 아니라 middleware를 반환하는 method라는 점에 주목하자.

이 때 반환되는 middleware는 모든 body를 buffer 형태로 parsing한 다음 여기서 requests만을 취한다고 한다. buffer는 데이터 모음 단위 정도로 이해된다.

https://nodejs.org/ko/docs/guides/anatomy-of-an-http-transaction/
https://thebook.io/080229/ch03/06/02-02/
참조

7)

class Server {
  public app: express.Application;

  constructor() {
    const app: express.Application = express();
    this.app = app;
  }

  private setRoute() {
    this.app.use(catsRouter);
  }

  private setMiddleware() {
    //* loggin middleware
    this.app.use((req, res, next) => {
      console.log(req.rawHeaders[1]);
      console.log("this is logging middleware");
      next();
    });

    //* json middleware
    this.app.use(express.json());

    this.setRoute();

    //* 404 middleware
    this.app.use((req, res, next) => {
      console.log("this is error middleware");
      res.send({ error: "404 not found error" });
    });
  }

  public listen() {
    this.setMiddleware();
    this.app.listen(8000, () => {
      console.log("server is on...");
    });
  }
}

function init() {
  const server = new Server();
  server.listen();
}

init();

싱글톤 패턴으로 서버를 구동하고 유사 3계층 패턴으로 router와 service-logic을 정리한 모습이다. 싱글톤 패턴은 서버 생성자가 여러번 실행 되더라도 최초 생성된 instance 객체를 사용하므로 메모리를 1회 사용한다는 장점이 있다.

cats 폴더에 해당 domain 관련 기능들을 모아 두었고, cats.route.ts와 같은 파일 명명법은 NestJS에서 사용되는 것임을 알아두자.

0개의 댓글