ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Express.js] 미들웨어(Middleware)
    Back-End/Express.js 2021. 2. 15. 01:17
    반응형

    마지막으로, Express의 기본개념인 미들웨어에 대해 공부한 내용을 정리하려고 한다.

     

    프로젝트 서버를 구축하기 위해 Express를 공부하면서, 미들웨어라는 단어를 접할수록 혼란스러웠다.

    Router에서 app.use() 내 함수도 미들웨어, body-parser 나 cors 들도 미들웨어(Third-Party) 라고 명명하는 것이었다.

     

    애써 무시하고 기능들을 구현했었지만, Express 기본학습을 마무리하기 전에 미들웨어의 개념을 한 번 정리할 필요성을 느꼈다.


    📗 미들웨어(Middleware) 란?

    Express 공식문서에서는 미들웨어를 아래와 같이 정의한다.

     

    Express는 자체적인 최소한의 기능을 갖춘 라우팅 및 미들웨어 웹 프레임워크이며, Express 애플리케이션은 기본적으로 일련의 미들웨어 함수 호출입니다.

    미들웨어 함수는 요청 오브젝트(req), 응답 오브젝트 (res), 그리고 애플리케이션의 요청-응답 주기 중 그 다음의 미들웨어 함수 대한 액세스 권한을 갖는 함수입니다. 그 다음의 미들웨어 함수는 일반적으로 next라는 이름의 변수로 표시됩니다.

     

    즉, 클라이언트에서 Request가 들어오고, 이를 Response로 반환하기 까지 사이에 거치는 모든 함수를 미들웨어라 하는 것이었다! 

    // app.js
    
    app.use(logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    app.use(cors());
    
    app.use('/', indexRouter);
    app.use('/users', usersRouter);
    app.use('/rests', restsRouter);

    내 프로젝트의 app.js 파일의 한 부분이다. 위쪽의 기본구성 및 cors() 는 express에서 제공하거나 npm에서 설치한 미들웨어들이다.

    아래의 usersRouter, restsRouter 역시 미들웨어이지만, 이는 첫 번째 인자에서 알 수 있듯 특정 path에서만 작동되는 것이다.

     

    - 미들웨어 문법

    출처: psyhm님의 블로그 (https://psyhm.tistory.com/8?category=654716)

     

    HTTP 메서드

    미들웨어가 작동할 path

    미들웨어 함수

     

     

     

    다음 미들웨어로의 액세스 함수

    HTTP response 객체

    HTTP request 객체

     

     

    위는, 내가 프로젝트 중 Router 모듈에서 많이 사용한 미들웨어 함수 호출을 위한 요소들이다.

    이전 Router에서 공부했듯, app(express)의 HTTP 메서드들은 첫 번째 인자로 path값을, 두 번째 인자로 미들웨어 함수를 받는 것이다.

     

     

     * next() 함수에 대해

     

    next() 는, 현재 미들웨어에서 처리한 요청을 다음 미들웨어로 넘기기 위해 사용되는 메서드이다. 다음 예제로 알아보자

    var express = require('express');
    var app = express();
    
    var requestTime = function (req, res, next) {
      req.requestTime = Date.now();
      next();
    };
    
    app.use(requestTime);
    
    app.get('/', function (req, res) {
      var responseText = 'Hello World!';
      responseText += 'Requested at: ' + req.requestTime + '';
      res.send(responseText);
    });
    1. requestTime 이라는 미들웨어가 있다. Request 객체에, requestTime key와, 값으로는 현재 시간을 할당한다.
    2. 이 미들웨어에는 next() 메서드로 종료된다. 현재 메서드에서 가공된 요청(requestTime)이 다음 미들웨어로 넘어간다.
    3. app.get() 메서드 내 함수도 미들웨어이다. 여기서는 responseText 문자열에 Request로 수정해서 Response로 반환한다.
    4. 이 때, 이전 미들웨어에서 넘어온 requestTime을 받을 수 있기 때문에, 타임스탬프가 추가된 문자열이 반환될 것이다.

    이 next() 함수로 인해 현재의 요청을 다음 미들웨어로 넘길 수 있다. (미들웨어 간 배치나 미들웨어 내 next() 위치가 중요!)

    이를 공부하니, 프로젝트 초반 Router() 미들웨어 함수들에 next() 를 넣지 않아서 Response가 넘어오지 않았던 것이 문득 생각났다.

     

     

    * 미들웨어(Middleware) vs 모듈(Module) ?

     

    미들웨어는 공부했듯이, 요청과 응답 사이에서 중간처리를 하는 모든 함수들을 미들웨어라고 칭하는 것을 알 수가 있었다.

    모듈은, 이러한 함수들의 묶음이라고 생각하면 된다. 기본적으로 제공되거나 혹은 필요에 의해 추가된 함수들의 집합체인 것이다.

    • 내장 모듈: Node.js 에서 기본적으로 지원하는 모듈들
    • 확장 모듈: Node.js 사용자들이 필요에 의해 만들어낸 오픈소스 기반의 모듈들

     

    - 미들웨어의 역할

    • 모든 코드를 실행 : Request와 Response 사이에 미들웨어 함수 내 코드들이 실행된다.
    • 다음 미들웨어 호출 : next() 메서드를 통해 현재 미들웨어를 종료, 다음 미들웨어로 요청이 넘어간다.
    • res, req 객체 변경 가능 : 미들웨어는 (req, res) 를 인자로 받는다. 이를 통해, request 수정이나 response 작성 등이 가능하다.
    • 요청-응답 주기를 종료 : Response 메서드를 통해 미들웨어의 실행을 종료시킬 수 있다. (res.send, res.json..)

    📗 미들웨어(Middleware) 유형

    Express 에서는 다음과 같이 크게 4가지 타입의 미들웨어를 구분한다. (미들웨어가 다뤄지는 영역이나 레벨을 기준으로 나눈 것이다.)

    1. 어플리케이션 레벨 미들웨어
    2. 라우터 레벨 미들웨어
    3. 오류 처리 미들웨어
    4. 써드파티 미들웨어

    1. 어플리케이션 레벨 미들웨어

    app.use() 나 app.METHOD() 의 함수를 이용해 미들웨어를 app객체 인스턴스에 바인드 시킨다. (METHOD = get, post..)

    // 예제1
    app.use(function (req, res, next) {
      console.log('Time:', Date.now());
      next();
    });
    
    // 예제2
    app.get('/user/:id', function (req, res, next) {
      res.send('USER');
    });

    예제1처럼 path가 없다면 모든 요청에서, 예제2는 해당 path로 들어온 요청에 대해서 미들웨어를 실행할 수 있다.

     

    미들웨어는 위처럼 하나가 아니라 여러개를 연결할 수 있고, 이를 조건부로 이동할 수 있다. 아래 예제를 보자.

    app.get('/user/:id', function (req, res, next) {
      if (req.params.id == 0) {
        next('route');
      }
      else next();
    }, function (req, res, next) {
      res.render('regular');
    });
    
    app.get('/user/:id', function (req, res, next) {
      res.render('special');
    });
    1.  첫 번째 app.get() 의 미들웨어가 실행된다. path parameter가 0이면 next('route'), 아니라면 next() 가 실행된다.
    2. next('route') 는 next() 의 문법이며, 현재 미들웨어의 남은 스택들을 종료하고 다음 라우트로 넘어가는 메서드다.
    3. 즉, 현재 미들웨어에 남은 렌더는 실행되지 않고, 다음 라우트의 res.render('special') 이 실행될 것이다.
    4. next()의 경우 다음 미들웨어 스택인 res.render('regular') 가 실행될 것이다.

     

    2. 라우터 레벨 미들웨어

    express.Router() 객체의 인스턴스에 바인드된다는 차이점을 제외하고, 어플리케이션 레벨과 동일하게 작동한다.

    마찬가지로, router.use() 혹은 router.METHOD() 함수 내에서 로드할 수 있다.

    var router = express.Router();
    
    router.use(function (req, res, next) {
      console.log('Time:', Date.now());
      next();
    });

     

    Router 객체는 반대로 그 자체도 미들웨어로 작동하기 때문에, app.use() 나 다른 router.use() 에서 사용될 수 있다.

     

    또한, Router 객체로 생성된 라우트 미들웨어는 app.use() 를 사용해 마운트 시켜야지만 사용할 수 있다.

    // app.js
    ...
    var usersRouter = require('./routes/users');
    ...
    app.use('/users', usersRouter);
    
    
    // users.js
    router.post('/signin', (req, res, next) => {
      try {
        const response = checkSignIn(req, usersDB);
        return res.json(response);
      }
      catch(err) {
        throw new Error(err);
      }
      next();
    })
    
    module.exports = router;

    이처럼, app.js 에선 모듈화된 users 라우트를 usersRouter 변수에 담는다. 또한, 이를 app.use() 메서드의 특정경로에서 실행한다.

    users.js 는 라우트 레벨 미들웨어들을 가지고 있으며, 이들을 마지막에 module.exports 해줘야 앱에서 활용가능하다.

     

    이렇게, 라우트를 따로 분리하는 장점은, 특정 root URL 기점으로 기능이나 로직별로 라우팅을 나눠서 관리할 수 있기 때문이다.

    (위 예시는, /users/signin 에서 실행되는 것이다. /users 경로 기준으로, 다양한 기능의 라우팅을 추가할 수 있음)

     

     

    3. 에러 핸들링 미들웨어

    말 그대로 오류 처리를 위한 미들웨어이다. 특별한 점은, (err, req, res, next) 네 개의 인자를 받아야 한다는 점이다.

    next() 메서드가 필히 사용되진 않지만, 시그니처를 유지하기 위해 포함해야 하고 없다면 에러 핸들링 미들웨어로 인식되지 않는다.

    // catch 404 and forward to error handler
    app.use(function(req, res, next) {
      next(createError(404));
    });
    
    // error handler
    app.use(function(err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};
    
      // render the error page
      res.status(err.status || 500);
      res.render('error');
    });

    express-generator 로 초기세팅을 했을때, app.js 맨 마지막에 작성된 이 부분이 에러 핸들링 미들웨어였던 것이다.

     

     

    4. 서드 파티 미들웨어

    Express 에 기능을 추가하기 위해 설치하는 미들웨어들이다.

    필요한 모듈은 Node.js 에서 설치하고, 애플리케이션 혹은 라우터 레벨에 포함시키면 된다. (아래 예제는 각각 app, router)

    // npm
    npm i cors
    
    // express.js
    const cors = require('cors');
    
    app.use(cors());
    // npm
    npm i bcrypt
    
    // express.js
    const bcrypt = require('bcrypt');
    
    router.post('/signup', async (req, res, next) => {
      try {
    	if {
          ...
        }
        else {
          response = successMessage();
          const hashedPassword = await bcrypt.hash(body.password, 10);
          usersDB.push({
            id: Date.now().toString(),
            ...body,
            password: hashedPassword,
          })
        }
      ...
    }

    정말 답답했던 혈이 뚫리는 기분이다!! 😃😃😃

    미들웨어라는 말이 메서드같기도 하고, 라이브러리 같기도 해서 정~말 모호하던 참이었다. (결론은 둘 다 맞았던 것)

    한편으론, 이를 확실히 공부하고 라우트를 작성했다면 좀 더 깔끔한 코드가 되지 않았을까 하는 아쉬움도 남는다. (리팩토링 고고!!)

     

    아직 Express 기본에 관해 포스팅할 내용들이 많이 남았다. (공식문서에 있는 오류처리나, 4버전 / 5버전에 대한 내용들)

    3차 프로젝트에 돌입하면, 지금까지 공부한 내용들과 앞으로의 학습내용들을 종합해서 좀 더 나은 서버를 만들어봐야겠다.

     

    [출처]

    - express.js 공식문서: 미들웨어 작성 - expressjs.com/ko/guide/writing-middleware.html  

                                      미들웨어 사용 - expressjs.com/ko/guide/using-middleware.html  

    - 라봉이 님의 블로그: psyhm.tistory.com/8?category=654716  

    - jinbroing 님의 블로그: jinbroing.tistory.com/126

     

    반응형
Designed by Tistory.