Nest.js Express 핵심원리

김태훈·2023년 3월 27일
0

Nest.js 배우기

목록 보기
2/2

해당 게시글은 인프런 '윤상석' 님의 Nest.js 강의를 보고 공부내용을 정리하였습니다.
https://www.inflearn.com/course/%ED%83%84%ED%83%84%ED%95%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EB%84%A4%EC%8A%A4%ED%8A%B8/dashboard

1. Express 서버를 TypeScript로 구동하기

import * as express from "express";
const app: express.Express = express();
const port: number = 8000;

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

app.get() 설명

클라이언트에서 "/" 경로로 HTTP GET요청을 보내면, 서버는(app) 이에 맞는 response를 보낸다.
그리고 req같은 경우는 클라이언트에서 보낸 요청 사항이 전달된다.

이때 String, JSON 객체 둘다 보낼 수 있다.

2. 데이터 모킹하기

데이터 모킹이란, 실제 데이터가 아닌 테스트를 실행하기 위해 개발자가 필요로 만든 데이터를 의미한다.

실제로는 데이터를 MySQL, MongoDB와 같은 데이터베이스에서 가져오지만, 지금은 로컬 메모리상에 존재하는 JS파일내에 데이터를 정의해서 해당 과정을 진행해보자.

1. 간단한 예시

import * as express from "express";

const app: express.Express = express();

const data = [1, 2, 3, 4];

app.get("/", (req: express.Request, res: express.Response) => {
  console.log(req);
  res.send({ data }); //key와 value가 같은 JSON이면 이렇게 줄여 쓰는 것도 가능하다.
});

app.listen(8000, () => {
  console.log("server is running");
});

2. 로컬에서 고양이 데이터 가져오기

고양이 데이터가 존재하는 해당 JS 파일

export type CatType = {
  id: string;
  name: string;
  age: number;
  species: string;
  isCute: boolean;
  friends: string[];
};

export const Cat: CatType[] = [
  {
    id: "fsduifh",
    name: "blue",
    age: 8,
    species: "Russian Blue",
    isCute: true,
    friends: ["asdfhj29009", "WE09tju2j"],
  },
  {
    id: "iohf2309q4hr",
    name: "som",
    age: 4,
    species: "Sphynx cat",
    isCute: true,
    friends: ["weju0fj20qj", "asdfhj29009", "weju0fj20qj"],
  },
  {
    id: "WE09tju2j",
    name: "lean",
    age: 6,
    species: "Munchkin",
    isCute: false,
    friends: [],
  },
  {
    id: "asdfhj29009",
    name: "star",
    age: 10,
    species: "Scottish Fold",
    isCute: true,
    friends: ["weju0fj20qj"],
  },
  {
    id: "weju0fj20qj",
    name: "red",
    age: 2,
    species: "Sharm",
    isCute: false,
    friends: [],
  },
];

1. 데이터 가져오는 코드

import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

const data = [1, 2, 3, 4];

app.get("/", (req: express.Request, res: express.Response) => {
  console.log(req);
  res.send({ cats: Cat });
});

app.listen(8000, () => {
  console.log("server is running");
});

2. 결과

3. express 미들 웨어 이해하기

1. 미들웨어란

양쪽을 연결하여 데이터를 주고 받을 수 있도록 중간에서 매개하는 역할을 한다.

예시

app.use((req,res,next)=>{
  next();
});

2. 미들웨어가 없다면

import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

const data = [1, 2, 3, 4];

app.get("/", (req: express.Request, res: express.Response) => {
  console.log(req);
  res.send({ cats: Cat });
});

app.get("/blue", (req, res) => {
  console.log(req.rawHeaders[1]);
  res.send({ blue: Cat[0] });
});

app.get("/som", (req, res) => {
  console.log(req.rawHeaders[1]);
  res.send({ som: Cat[1] });
});
app.listen(8000, () => {
  console.log("server is running");
});
  • blue
  • som

지금은 API가 끽해야 두개정도라 log를 찍는게 귀찮지 않다.
하지만 API가 많아지면 문제가 생기는데, 그럴 경우 미들웨어를 사용하면 좋다.

3. 미들웨어의 원리

현재 존재하는 라우터는 총 세가지

  • "/"
  • "/blue"
  • "/som"

이 때 만일, /som 으로 요청을 보냈으면 라우터를 뒤지면서 "/som"과 관련된 라우터를 찾고 해당 비즈니스 로직이 실행이 된다. 그 후 적절한 응답을 프론트(클라이언트)쪽에 넘겨주게 된다.
이 때, 각 라우터 마다 동일한 코드가 존재하는 상황 에서는 해당 코드의 중복을 미들웨어로 막을 수 있다.
라우터 이전에 '미들웨어'를 따로 만들고 프론트엔드가 라우터에게 요청을 보낼때 미들웨어가 먼저 요청을 받게 하면 된다. 그 후 목표 라우터를 찾는다.
즉 다음과 같다.

프론트 -----> 미들웨어 -----> 라우터들

4. 미들웨어 코드

1. 전체 미들웨어

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

이렇게 하면, 프론트에서 보낸 요청이 먼저 해당 코드를 실행하고, 그 후에 next( ) 함수를 실행하여, 라우터를 찾는다.

2. 라우터 미들웨어

또한, 그냥 app. ~ 함수에 인자로 next만 건네면 미들웨어가 된다.

app.get("/som", (req, res, next) => {
  console.log(req.rawHeaders[1]);
  console.log("this is som middleware");
  next();
});

5. 예시

1. "/som" 으로 요청을 보낼 때

2. 주의해야할 점

미들웨어를 먼저 써야함 !!
먼저 라우팅 정보를 앞에서 찾게 되면 거기서 통신을 끊어버린다.

그래서 미들웨어 코드를 라우터 앞에 배치하자

3. 두가지 미들웨어 테스트

import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

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

app.get("/som", (req, res, next) => {
  console.log(req.rawHeaders[1]);
  console.log("this is som middleware");
  next();
});

app.get("/", (req: express.Request, res: express.Response) => {
  console.log(req);
  res.send({ cats: Cat });
});

app.get("/blue", (req, res) => {
  console.log(req.rawHeaders[1]);
  res.send({ blue: Cat[0] });
});

app.get("/som", (req, res) => {
  console.log(req.rawHeaders[1]);
  res.send({ som: Cat[1] });
});
app.listen(8000, () => {
  console.log("server is running");
});

6. 응용

라우터정보에 존재하지 않은 URI로 접속시에 에러처리도 미들웨어로 할 수 있다.
이는, 맨 마지막에 미들웨어 코드를 작성하는 방식이다.

import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

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

app.get("/som", (req, res, next) => {
  console.log(req.rawHeaders[1]);
  console.log("this is som middleware");
  next();
});

app.get("/", (req: express.Request, res: express.Response) => {
  console.log(req);
  res.send({ cats: Cat });
});

app.get("/blue", (req, res) => {
  console.log(req.rawHeaders[1]);
  res.send({ blue: Cat[0] });
});

app.get("/som", (req, res) => {
  console.log("som:", req.rawHeaders[1]);
  res.send({ som: Cat[1] });
});

app.use((req, res, next) => {
  res.send("ERROR: 404 not found error");
});
app.listen(8000, () => {
  console.log("server is running");
});

4. 고양이 데이터 Read API 개발

1. 전체 고양이 정보 READ

1. 정상작동 API

import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

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

//* READ 고양이 전체 데이터 조회
app.get("/cats", (req, res) => {
  try {
    const cats = Cat;
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
});

app.listen(8000, () => {
  console.log("server is running");
});

2. 오류 발생시킨 API

import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

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

//* READ 고양이 전체 데이터 조회
app.get("/cats", (req, res) => {
  try {
    const cats = Cat;
    throw new Error("db connection error");
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
});

app.listen(8000, () => {
  console.log("server is running");
});

2. 특정 고양이 정보 READ

클라이언트에서 특정 고양이를 조회하고 싶다고 정보를 넘길때, '동적'으로 고양이 정보를 라우팅할 필요가 있다.

이 때, req안에는 당연히 요청에 대한 정보(URI)가 있으므로 이를 이용하여 cat id를 넘긴다.

app.get("/cats/:catid",(req,res)=>{
});

다음과 같이 " : " 를 붙여서 동적으로 라우팅 시킨다.

import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

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

//* READ 고양이 전체 데이터 조회
app.get("/cats", (req, res) => {
  try {
    const cats = Cat;
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
});

// * READ 특정 고양이 조회
app.get("/cats/:catid", (req, res) => {
  try {
    const params = req.params; 
    console.log(params);
    const cat = Cat.find((cat) => {
      return cat.id === params.catid;
    });
    res.status(200).send({
      success: true,
      data: {
        cat,
      },
    });
  } catch (error) {}
});
app.listen(8000, () => {
  console.log("server is running");
});

req를 찍었을 때, 나오는 것

이중에서 params가 동적라우팅 정보를 의미한다.

5. 고양이 데이터 CREATE API 개발

1. 맛보기

import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

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

//* READ 고양이 전체 데이터 조회
app.get("/cats", (req, res) => {
  try {
    const cats = Cat;
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
});

// * READ 특정 고양이 조회
app.get("/cats/:catid", (req, res) => {
  try {
    const params = req.params;
    console.log(req);
    const cat = Cat.find((cat) => {
      return cat.id === params.catid;
    });
    res.status(200).send({
      success: true,
      data: {
        cat,
      },
    });
  } catch (error) {}
});

//* CREATE API 추가
app.post("/cats", (req, res) => {
  try {
    const data = req.body;
    console.log(data);
    res.status(200).send({
      success: true,
      data,
    });
  } catch (error) {}
});
app.listen(8000, () => {
  console.log("server is running");
});
이렇게 보내보았는데, 콘솔에 data를 찍으면 undefined다.

2. express에서 JSON 읽을 수 있게 하는 미들웨어 추가

express 서버 자체에서 JSON을 읽을 수 없으므로, 미들웨어를 추가하자.

 import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

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

//* JSON 미들웨어
app.use(express.json());

//* READ 고양이 전체 데이터 조회
app.get("/cats", (req, res) => {
  try {
    const cats = Cat;
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
});

// * READ 특정 고양이 조회
app.get("/cats/:catid", (req, res) => {
  try {
    const params = req.params;
    console.log(req);
    const cat = Cat.find((cat) => {
      return cat.id === params.catid;
    });
    res.status(200).send({
      success: true,
      data: {
        cat,
      },
    });
  } catch (error) {}
});

//* CREATE API 추가
app.post("/cats", (req, res) => {
  try {
    const data = req.body;
    console.log(data);
    res.status(200).send({
      success: true,
      data,
    });
  } catch (error) {}
});
app.listen(8000, () => {
  console.log("server is running");
});

3. CREAE 코드 추가 (단 한줄)

import * as express from "express";
import { Cat } from "./cat_model";
const app: express.Express = express();

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

//* JSON 미들웨어
app.use(express.json());

//* READ 고양이 전체 데이터 조회
app.get("/cats", (req, res) => {
  try {
    const cats = Cat;
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
});

// * READ 특정 고양이 조회
app.get("/cats/:catid", (req, res) => {
  try {
    const params = req.params;
    console.log(req);
    const cat = Cat.find((cat) => {
      return cat.id === params.catid;
    });
    res.status(200).send({
      success: true,
      data: {
        cat,
      },
    });
  } catch (error) {}
});

//* CREATE API 추가
app.post("/cats", (req, res) => {
  try {
    const data = req.body;
    Cat.push(data); // -------------CREATE
    console.log(data);
    res.status(200).send({
      success: true,
      data,
    });
  } catch (error) {}
});
app.listen(8000, () => {
  console.log("server is running");
});

6. 고양이 라우트 분리

1. 문제점 => 방금과 같이 라우터가 한 파일에 많이 존재하게 되면, 관리하기 매우 복잡해짐

따라서, 라우터를 분리해보자 !

2. 코드

미들웨어같은 경우에는 "app.ts" 에 그대로 두고, cat과 관련된 라우터들을 따로 "cats" 디렉토리로 뽑아내자.

1. cats.route.ts 코드

import { Cat } from "./cats_model";

//* READ 고양이 전체 데이터 조회
app.get("/cats", (req, res) => {
  try {
    const cats = Cat;
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
});

// * READ 특정 고양이 조회
app.get("/cats/:catid", (req, res) => {
  try {
    const params = req.params;
    console.log(req);
    const cat = Cat.find((cat) => {
      return cat.id === params.catid;
    });
    res.status(200).send({
      success: true,
      data: {
        cat,
      },
    });
  } catch (error) {}
});

//* CREATE API 추가
app.post("/cats", (req, res) => {
  try {
    const data = req.body;
    Cat.push(data); // -------------CREATE
    console.log(data);
    res.status(200).send({
      success: true,
      data,
    });
  } catch (error) {}
});

문제 : app은 서버 정보인데, 이를 어떻게 불러올 것인가?

2. {Router}를 활용하여 app 서버에 라우터 정보 등록하기

1. app.route.ts 에 Router 인스턴스로 라우팅정보 등록

import { Cat } from "./cats_model";
import { Router } from "express";
const router = Router();
//* READ 고양이 전체 데이터 조회
router.get("/cats", (req, res) => {
  try {
    const cats = Cat;
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
});

// * READ 특정 고양이 조회
router.get("/cats/:catid", (req, res) => {
  try {
    const params = req.params;
    console.log(req);
    const cat = Cat.find((cat) => {
      return cat.id === params.catid;
    });
    res.status(200).send({
      success: true,
      data: {
        cat,
      },
    });
  } catch (error) {}
});

//* CREATE API 추가
router.post("/cats", (req, res) => {
  try {
    const data = req.body;
    Cat.push(data); // -------------CREATE
    console.log(data);
    res.status(200).send({
      success: true,
      data,
    });
  } catch (error) {}
});

export default router;

Router 관련 인스턴스를 하나 만들고,
해당 인스턴스에 라우터 정보들을 등록후,
export로 내보낸다.

2. cats.model.ts 에서 export한 라우터를 app.ts에서 import하여 사용

import * as express from "express";
import router from "./cats/cats.route";
const app: express.Express = express();

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

//* JSON 미들웨어
app.use(express.json());

app.use(router);

app.listen(8000, () => {
  console.log("server is running");
});

7. 고양이 UPDATE API 추가

1. 전체 업데이트 => PUT

router.put("/cats/:catid", (req, res) => {
  try {
    const params = req.params;
    const body = req.body;
    let result;
    Cat.forEach((cat) => {
      if (cat.id === params.catid) {
        cat = body;
        result = cat;
      }
    });
    res.status(200).send({
      success: true,
      data: {
        cat: result,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      data: {
        cat: "nodata",
      },
    });
  }
});

2. 부분 업데이트 => PATCH

router.patch("/cats/:catid", (req, res) => {
  try {
    const params = req.params;
    const body = req.body;
    let result;
    Cat.forEach((cat) => {
      if (cat.id === params.catid) {
        cat = { ...cat, ...body }; //구조분해
        result = cat;
      }
    });
    res.status(200).send({
      success: true,
      data: {
        cat: result,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      data: {
        cat: "nodata",
      },
    });
  }
});
cat={...cat,...body}

의 뜻은 cat 과 body를 구조분해 할당 후 기존의 키에서 중복된 키에 대한 value값을 바꾼다.

3. 정보 삭제 => DELETE

router.delete("/cats/:catid", (req, res) => {
  try {
    console.log(req);
    const params = req.params;
    console.log(params);
    const newCat = Cat.filter((cat) => cat.id !== params.catid);
    res.status(200).send({
      success: true,
      data: {
        cat: newCat,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      data: {
        cat: "nodata",
      },
    });
  }
});

8. express 싱글톤 패턴, 서비스 패턴

1. 싱글톤 패턴

객체의 인스턴스가 오직 한개만 생성되게 하는 패턴
이렇게 함으로써, 다음과 같은 효과를 얻을 수 있다.

  • 메모리 낭비 방지
  • 다른 클래스간의 데이터 공유가 쉬워짐

2. 우리가 만든 싱글톤 인스턴스

우리가 코드로 작성한 싱글톤 인스턴스는 'app'이다.
이를 싱글톤 패턴으로 바꾸면 다음과 같다.

import * as express from "express";
import catsRouter from "./cats/cats.route";

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

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

    //* JSON 미들웨어
    this.app.use(express.json());

    this.setRoute();

    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 running");
    });
  }
}

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

2. 라우터에 서비스 패턴 적용하기

1. cats.service.ts 생성하여, 비즈니스 로직같은 것들을 해당 파일에 함수로 저장

import { Cat } from "./cats_model";
import { Request, Response } from "express";
export const readAllCat = (req: Request, res: Response) => {
  try {
    const cats = Cat;
    res.status(200).send({
      success: true,
      data: {
        cats,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      error: error.message,
    });
  }
};

// * READ 특정 고양이 조회
export const readCat = (req: Request, res: Response) => {
  try {
    const params = req.params;
    console.log(params);
    const cat = Cat.find((cat) => {
      return cat.id === params.catid;
    });
    res.status(200).send({
      success: true,
      data: {
        cat,
      },
    });
  } catch (error) {}
};

//* CREATE API 추가
export const createCat = (req: Request, res: Response) => {
  try {
    const data = req.body;
    Cat.push(data); // -------------CREATE
    console.log(data);
    res.status(200).send({
      success: true,
      data,
    });
  } catch (error) {}
};

//* UPDATE API 추가 => PUT 메소드
export const updateCat = (req: Request, res: Response) => {
  try {
    const params = req.params;
    const body = req.body;
    let result;
    Cat.forEach((cat) => {
      if (cat.id === params.catid) {
        cat = body;
        result = cat;
      }
    });
    res.status(200).send({
      success: true,
      data: {
        cat: result,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      data: {
        cat: "nodata",
      },
    });
  }
};
//* 부분적 UPDATE API 추가 => PATCH 메소드
export const updatePartialCat = (req: Request, res: Response) => {
  try {
    const params = req.params;
    const body = req.body;
    let result;
    Cat.forEach((cat) => {
      if (cat.id === params.catid) {
        cat = { ...cat, ...body };
        result = cat;
      }
    });
    res.status(200).send({
      success: true,
      data: {
        cat: result,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      data: {
        cat: "nodata",
      },
    });
  }
};
//* DELETE API 추가 => DELETE 메소드
export const deleteCat = (req: Request, res: Response) => {
  try {
    console.log(req);
    const params = req.params;
    console.log(params);
    const newCat = Cat.filter((cat) => cat.id !== params.catid);
    res.status(200).send({
      success: true,
      data: {
        cat: newCat,
      },
    });
  } catch (error) {
    res.status(400).send({
      success: false,
      data: {
        cat: "nodata",
      },
    });
  }
};

2. 분리한 비즈니스 로직을, 라우트 설정 파일에 불러옴

import { Router } from "express";
import {
  readAllCat,
  readCat,
  createCat,
  updateCat,
  updatePartialCat,
  deleteCat,
} from "./cats.service";
const router = Router();
//* READ 고양이 전체 데이터 조회
router.get("/cats", readAllCat);

// * READ 특정 고양이 조회
router.get("/cats/:catid", readCat);

//* CREATE API 추가
router.post("/cats", createCat);

//* UPDATE API 추가 => PUT 메소드
router.put("/cats/:catid", updateCat);
//* 부분적 UPDATE API 추가 => PATCH 메소드
router.patch("/cats/:catid", updatePartialCat);
//* DELETE API 추가 => DELETE 메소드
router.delete("/cats/:catid", deleteCat);

export default router;

모든 import 단축키 : "Ctrl" + "."

profile
기록하고, 공유합시다

0개의 댓글