Express.js의 미들웨어(Middleware)

Express 애플리케이션은 기본적으로 일련의 미들웨어 함수 호출입니다

2020/01/04

참고자료


  • Express의 미들웨어에 대해 다룹니다.

1. Intro

image

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

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

미들웨어의 로드 순서는 중요하며, 먼저 로드되는 미들웨어 함수가 먼저 실행됩니다.

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000);

위 코드는 Express의 가장 간단한 Hello World의 예시이며, 아래에서 이에 대한 두가지 미들웨어(logger, requestTime)를 다룰 예정입니다.

2-1. 예시 (Logger)

앱이 요청을 수신할 때마다, LOGGED라는 메시지를 터미널에 출력하는 미들웨어를 만들어봅시다.

var express = require('express');
var app = express();

var myLogger = function (req, res, next) {
  console.log('LOGGED');
  next();
};

app.use(myLogger); // 여기서 미들웨어를 호출!!

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000);

myLogger라는 함수(미들웨어)는 단순히 메시지를 출력한 후,
next() 함수를 호출하여 스택 내의 그 다음 미들웨어 함수에 요청을 전달합니다.

이 함수(미들웨어)를 로드하려면, app.use()의 인자로 myLogger를 넣은 후, 호출합니다.

루트 경로(/)로 라우팅하기 전에 미들웨어 함수를 로드해야 합니다.
루트 경로에 대한 라우팅 이후에 myLogger가 로드되면, 루트 경로의 라우트 핸들러가 요청-응답 주기를 종료(res.send)하므로 요청은 절대로 myLogger에 도달하지 못하며 앱은 LOGGED를 출력하지 않습니다.

2-2. 예시 (requestTime)

앱의 루트에 대한 요청을 실행할 때, 타임스탬프를 브라우저에 표시하도록 미들웨어를 추가합니다.

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

app.listen(3000);

이제 앱은 requestTime 미들웨어 함수를 사용합니다. 또한 루트 경로 라우트의 콜백 함수는 미들웨어 함수가 req(요청 오브젝트)에 추가하는 특성을 사용합니다.

이와같이 Express는 일련의 미들웨어 함수들을 호출하는것으로 요청에대한 응답을 만들어냅니다.

3-1. 에러 핸들러로 에러 보내기

image

에러가 발생하지 않는다면, 에러를 핸들링하는 미들웨어를 지나칩니다.

image

에러가 발생한다면, 그 즉시 에러 핸들러로 이동하게 됩니다.

에러 핸들러를 동작시키는 방법은 아래와 같이 두가지가 있습니다.
(아래의 코드는 에러 핸들러가 아닌, 에러 핸들러로 가기 이전 단계의 코드입니다.)

  1. throw를 통해 Error를 발생시켜서 전달.
app.get('/', function (req, res) {
  throw new Error('BROKEN') // 에러가 발생하면, Express가 알아서 캐치합니다!
})
  1. next 함수의 인자로 넘겨서 전달.
app.get('/', function (req, res, next) {
  fs.readFile('/file-does-not-exist', function (err, data) {
    if (err) {
      next(err) // Express의 에러핸들러로 이동합니다.
    } else {
      res.send(data)
    }
  })
})

3-2. 에러 핸들러 만들기

이제 에러 핸들러를 직접 만들어봅시다.
에러 핸들러는 req, res, next 에 err라는 인자를 추가로 받습니다. (실제로는 err 인자가 첫번째로 위치하게 됩니다.)

var bodyParser = require('body-parser')

function logErrors (err, req, res, next) {
  console.error(err.stack)
  next(err)
}

function clientErrorHandler (err, req, res, next) {
  if (req.xhr) {
    res.status(500).send({ error: 'Something failed!' })
  } else {
    next(err)
  }
}

function errorHandler (err, req, res, next) {
  res.status(500)
  res.render('error', { error: err })
}

app.use(bodyParser.urlencoded({
  extended: true
}));
app.use(bodyParser.json());

app.get('/', (req, res, next) => {
  PaidContent.find(function (err, doc) {
    if (err) {
      return next(err);
    } else {
      res.json(doc);
    }
  })
});

app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);

에러 핸들러는 모든 미들웨어 중 가장 아래에 배치시킵니다.

아래 두장의 이미지는 Express 미들웨어를 가장 잘 설명하고 있습니다.

image Node - Express 커스텀 미들웨어 만들기 image Express Middlewares, Demystified

다음 포스팅에서는 라우팅에 대해 다룰 예정입니다.