Wecode - Project 2 (부트캠프)/독학

Project2: Middleware [개념]

Queen Julia 2023. 9. 24. 14:50

Middleware란?

 

API는 요청이 들어왔을 때부터 응답으로 내보내기 까지의 전 과정을 처리합니다.

그 안에서 여러 함수들을 차례대로 거치게 되는데 그 함수 하나를 middleware고 합니다.

 

미들웨어에서 다음 기능으로 통과시킬지 말지 결정 할 수 있습니다.

이를 응용하면 미들웨어를 이용하여 여러 기능실행 전 수행해야할 기능을 만들 수 있습니다.

 

용도

1. req.body 추가 (app.use(express.json());)

postman 사용시 raw - json으로 body 값을 보냈을 겁니다. 하지만 http의 특성상 모든 값들은 string으로 전송됩니다. 그래서 원래라면 string을 json 형태로 바꾸어서 req.body 안에 넣는 기능을 따로 구현했어야 했습니다. 이는 모든 요청에서 사용될 수 있기 때문에 express에서 미들웨어 형태로 제공하게 되었습니다.

 

2. 인증, 인가

인증 인가시, custom 미들웨어를 통해 인증과 인가 기능을 구현할 수 있습니다. 이는 인증과 인가가 필요한 API 각각 해당 기능을 구현할 필요 없이 인증을 통과하면 해당 API 기능을 실행할 수 있게 만들면 됩니다.

 

 

3. CORS

CORS는 브라우저의 보안 정책입니다. 해당 서버의 자원을 들고 올 때, 기본적으로 같은 origin에서 접근하여야 해당 API를 호출할 수 있습니다. 그래서 항시 브라우저한테 해당 서버는 모든 요청에 대해서 개방되어 있다라는 내용을 전달하여야합니다. 모든 요청에서 사용하므로 해당 부분은 미들웨어로 사용할 수 있습니다.

이외에도 여러가지 용도로 사용할 수 있습니다.

 

 

 예시 코드

  1. req.body
const express = require('exress');

const app = express();

// bad location
app.post('/bad', (req, res, next) => {
	console.log(req.body); // undefined
	res.json({ message: "bad" });
});

// express 안에 json 메소드를 이용하여 미들웨어를 구성합니다.
// 아래와 같이 app 초기 부분에 use를 쓰면 모든 요청에 대해서 미들웨어를 수행합니다.
app.use(express.json());
// (err, req, res, next) => { req.body = ~; next(); }

// good location
app.post('/', (req, res, next) => {
	console.log(req.body); // json형식으로 보낸 body 객체가 출력됩니다.
	res.json({ message: "root" });
});

app.post('/good', (req, res, next) => {
	console.log(req.body); // json형식으로 보낸 body 객체가 출력됩니다.
	res.json({ message: "root" });
});

app.listen(10010);

 

2. 인증, 인가

async function authMiddleware(req, res, next) {
	const token = req.headers.authorization;
//  const { token } = req.headers;

	if ( !validToken(token) ) {
		res.status(400).json({"message": "invalid token"});
		return;
	}

	const userInfo = await getUser(token.id);

	if (userInfo) { // 로그인이 되었을 때
		req.userInfo = userInfo; // 다음으로 넘어가는 미들웨어 or 기능에서 req.isLogin 값을 사용할 수 있습니다.
		console.log(req.userInfo) // 36
		next(); // 다음 미들웨어 or 엔드 포인트로 넘어갑니다.
	}
	else { // 로그인에 실패하였을 때
		// 로그인에 실패했다면 다음 미들웨어로 넘어가지 않고 바로 응답을 보낼 수 있습니다.
		res.status(400).json({ message: "로그인할 수 없습니다." });
	}
}

 

3. CORS

const express = require('exress');
const cors = require('cors');

const app = express();

app.use(cors());
// cors 설정들이 적용됩니다.
// 세션때 자세히 다룰 예정입니다.

app.listen(10010);

예시 - 인증 / 인가 middleware

아래 함수(미들웨어)는 인증/인가 로직이 필요한 모든 작업 전에 거쳐야 할 단계입니다.

 

예를들어

  • 글을 쓸 때, 누가 글을 썼는가가 중요하고
  • 글을 수정할 때, 누가 글을 수정하는 요청을 했는가가 중요하고
  • 글을 삭제할 때, 누가 글을 삭제하는 요청을 했는가가 중요합니다.

유저가 로그인에 성공 했을 시에

우리는 클라이언트에게 토큰을 보냅니다.

그 토큰에는 유저의 고유한 id 를 암호화 해서 보냈었죠.

 

토큰은 HTTP 요청을 하는 사람이 누구인지를 증명하는 티켓과 같아서,

누구 인지를 밝히려면

매번 토큰을 담아서 요청합니다. (HTTP Stateless 속성 때문에)

 

 

그렇다면 인증 / 인가 (누구인지를 밝히는 작업) 로직을

모든 컨트롤러 모듈에 넣어줘야 할까요?

네 그렇게 해도 됩니다.

 

 

하지만,

계속 쓰이는 코드를 파일에 넣어서 

각 쓰이는 파일에는 연결하는 코드만 

 

이 중간 로직을 모듈화해서  필요한 곳에 맞게 사용하게 된다면 더욱더 효율적이고 좋은 코드가 됩니다.

 

middlewares 폴더의 auths.js 혹은 validateToken.js 파일

 

다양한 코드 

import { errorGenerator } from '../utils';
import { UserService } from '../services';
import jwt from 'jsonwebtoken';

const { AUTH_TOKEN_SALT } = process.env

const validateToken = async (req, res, next) => {
  try {
    const token = req.headers.authorization;
    const { id } = jwt.verify(token, AUTH_TOKEN_SALT) // 암호화된 토큰을 복호화 합니다.

    const foundUser = await UserService.findUser({ id })

    if (!foundUser) // 이 토큰을 가진 유저가 데이터베이스에 없으면 404 에러를 냅니다.
      errorGenerator({ statusCode: 404, message: 'USER_NOT_FOUND' })

    req.foundUser = foundUser // request 객체에 새로운 키값에 찾아진 유저의 정보를 할당하고
    next() // next() 함수로 다음 미들웨어로 맥락(context)를 연결합니다.
  } catch (err) {
    next(err)
  }
}

export default validateToken;

 

 

미들웨어에 공통 코드 뺀 자리에, 미들웨어 파일 연결하기

위에서 생성한 미들웨어를 Express 라우터에 연결하는 것은 어렵지 않습니다.

 

HTTP 요청이 컨트롤러로 넘어가기 전에 미들웨어의 이름대로 중간에 함수를 넣어주면 됩니다.

 

다른 방식의 코드 

  • routes/ArticleRouter.js

import express from 'express';
const { ArticleController } from '../controllers';
const { validateToken } from '../middlewares'; 
// 위에서 만든 미들웨어 로직을 임포트 합니다.

const router = express.Router()

router.use(validateToken); 
// 전체적
app.use(validateToken)

router.get('/', ArticleController.getArticles)
router.get('/:articleId', ArticleController.getOneArticle)

 

// 아래 세가지 post, put, delete 요청에 대한 컨트롤러는 유저의 인증/인가가 필요한 로직입니다.
router.post('/', validateToken, ArticleController.postOneArticle)
router.put('/:articleId', validateToken, ArticleController.updateOneArticle)
router.delete('/:articleId', validateToken, ArticleController.deleteOneArticle)
// 개별적으로 적용되는 미들웨어

export default router

import express from 'express';
const { ArticleController } from '../controllers';
const { validateToken } from '../middlewares'; // 위에서 만든 미들웨어 로직을 임포트 합니다.

const router = express.Router()

router.use(validateToken); // 전체적
app.use(validateToken)

router.get('/', ArticleController.getArticles)
router.get('/:articleId', ArticleController.getOneArticle)
**// 아래 세가지 post, put, delete 요청에 대한 컨트롤러는 유저의 인증/인가가 필요한 로직입니다.**
router.post('/', validateToken, ArticleController.postOneArticle)
router.put('/:articleId', validateToken, ArticleController.updateOneArticle)
router.delete('/:articleId', validateToken, ArticleController.deleteOneArticle)
// 개별적으로 적용되는 미들웨어

export default router

 

이 때 HTTP 요청의 맥락이 이어지게 하는 것이 핵심입니다.

그것을 가능하게 하는 것이 express 의 next() 함수이며, next() 에 의해 연결된 모든 함수는 요청이 끝날때(응답을 보낼 때)까지 동일한 request 객체를 공유하게 됩니다.

따라서 validateToken 함수에서 req 객체에 찾아진 유저의 정보를 저장해서 next() 함수로 보내는 것 입니다.

    req.foundUser = foundUser // request 객체에 새로운 키값에 찾아진 유저의 정보를 할당하고
    next() // next() 함수로 다음 미들웨어로 맥락(context)를 연결합니다.

 

 

 

에러 핸들링 w/ [asyncwrap, next, ErrorHandler.js ]

동기 분께서 설명해주신 부분 1) async를 쓸 때 controller.js 에서 try, catch 지우고 errorHandler.js 사용 ( 에러 시 일로 감) 1-1) errorHandler.js 에서 next 사용 X 1-2) errorHandler.js 에서 next 사용 2) async를 안 쓸 때

pm-developer-justdoit.tistory.com

 

middlewware 공부 , errorhandler, async

에러 핸들링 2 [적용] 1. throw로 에러 던져보기 에러를 던지는 방법으로 throw 이는 개발자가 작성하는 모듈에서 발생가능한 에러 상황에서 던지게 되며 상위 계층이나 호출하는 곳에서 모듈의 에

pm-developer-justdoit.tistory.com

 

에러 핸들링 [개념] 1

일반적으로 Javascript 에서는 try-catch문과 throw를 이용하여 에러를 처리 에러 핸들링 미들웨어는 에러를 처리하기 위해 반복적으로 진행되는 코드를 모듈화하여 소프트웨어의 확장성과 생산성을

pm-developer-justdoit.tistory.com

 

 

Project 3: [주문 api] - if 에러 핸들링, transaction

에러 핸들링 1. 2. 2-1) 느낌표 위치 2-2) 공백 주의 참고 3. 수량이 36개 남았을 때 남은 36개 다 넣었을 때 장바구니 0개가 됨 장바구니 carts 테이블에 원래 있던 carts id 2 가 사라짐 --> 재고가 없기에 3

pm-developer-justdoit.tistory.com

 

 

Project 3 - [주문/결제 api] 에러 핸들링 코드 및 과정

3가지 에러 1. // 에러 핸들링: carts 에 담기지 않은 product를 주문할 때 -> productId 라서 for()안에 들어가야 함 const isProductInCarts = await orderDao.isProductInCarts(userId, productId); // await: userId, productId,(orderDetai

pm-developer-justdoit.tistory.com