...
미들웨어 작성
미들웨어는 익스프레스의 핵심이다.
요청과 응답의 중간(middle, 미들)에 위치하여 미들웨어 라고 부른다.
미들웨어는 요청과 응답을 조작하여 기능을 추가하기도 하고, 나쁜 요청을 걸러내기도 한다.
익스프레스 내에서 웹 요청과 응답에 대한 정보를 사용해서 필요한 처리를 진행할 수 있도록 분리된 독립적인 함수이다.
그리고 각각의 미들웨어는 next() 메소드를 호출해서 그 다음 미들웨어가 작업을 처리할 수 있도록 순서를 넘길 수 있다.
위 사진에서, function(req, res, next) {} 부분이 바로 미들웨어 인 것이다.
⚠️ 뇌정지 당하기전의 예방지식!
노드를 배우면서 우리는 함수 인자에 콜백함수가 들어가 있는 형태를 자주자주 봐왔다.
이 함수는 비동기 함수가 처리되고 그 결과를 나타내기 위한 콜백함수이다.
대표적으로 노드 모듈 메서드인 http.createServer() 가 있다.
const server = http.createServer((req, res) => { // 이 함수는 그냥 처리결과를 하기위한 콜백함수
});
다음은 익스프레스 모듈 메서드인 app.get이다.
(req, res) => { } 생김새가 매우매우 비슷하지만, 사실 완전히 다른 함수이다.
이 함수는 익스프레스 전용 미들웨어 함수이며, 위의 비동기 처리 콜백 기능과는 아무런 상관이 없다.
이 함수는 next()를 통해 작동한다. 이를 유의하자
app.get('/', (req, res) => { // 이 함수는 익스프레스 전용 미들웨어 라는 함수이다.
// ...
}
- 요청과 응답의 중간에 위치하여 미들웨어
- 미들웨어는 req, res, next가 매개변수인 함수
- 미들웨어 함수를 여러번 인자로 쓸수 있음.
- req: 요청, res: 응답 조작 가능, next()로 다음 미들웨어로 넘어감.
미들웨어 기본 작성 예제)
var express = require('express');
var app = express();
// app.use가 미들웨어가 아니라 app.use()의 인자안의 함수가 미들웨어
app.use(function (req, res, next) {
req.requestTime = Date.now(); // req라는객체에 requestTime 키와 밸류를 래퍼로 등록. requestTime는 사용자가 정한 값이다.
next(); // 다음 미들웨어 함수를 작동
});
// app.get이 미들웨어가 아니라 app.get()의 인자안의 함수가 미들웨어
app.get('/', function (req, res) { // 위에서 next()가 호출되면 이 콜백함수가 작동
res.send(req.requestTime); // 위 미들웨어에서 requestTime 객체를 등록했고 next()를 사용했기 때문에 호출해서 데이터 사용 가능
});
// 서버 실행
app.listen(3000);
app.use()
app.use() 는 Express 앱에서 항상 실행하는 미들웨어 역할
app.get(), app.post()등과 달리 요청 URL을 지정하지 않아도 app.use()를 사용할 수 있으며 해당 경우에는 URL에 상관없이 매번 실행된다.
app.use() 및 app.Method() 함수를 이용해 응용프로그램 수준의 미들웨어를 app객체의 인스턴스에 바인딩Method = get or post
미들웨어 구조 작성 예제)
const express = require('express');
/* 1. 앱을 만든다. */
const app = express();
/* 2. 앱에 관련 설정 속성들을 만든다. */
app.set('port', process.env.PORT || 3000);
/* 3. 공통 미들웨어를 만든다. */
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됩니다.');
// next라는 세 번째 매개변수는 다음 미들웨어로 넘어가는 함수이다.
// next를 실행하지 않으면 다음 미들웨어가 실행되지 않는다.
next();
})
/* 4. 라우터들을 만든다. */
// /error 요청 올때 동작
app.get('/error', (req, res, next) => {
next(); // next()에 인수가 없다면, 바로 다음 미들웨어 함수로 넘어가게 된다.
}, (req, res) => { // 미들웨어를 여러개 넣어줘도 된다. 위에서 next()되면 실행 된다.
try {
// .. 에러 발생 코드
} catch(err) {
error(err); // next()에 인수가 있다면, 에러 처리 미들웨어로 점프하게 된다.
}
});
// /about 요청 올때 동작
app.get('/about', (req, res) => {
res.send('Hello, about');
});
// 주소 부분에는 정규표현식, : (콜론)을 사용한 와일드 카드도 적용이 가능하다 :변수명 정도로 생각하면 된다.
// 와일드 카드를 사용할때는 다른 라우터 보다 뒤에 적어주는 것이 좋다
app.get('/category/:name', (req, res) => {
res.send(`Hello, ${req.params.name}`);
});
// 모든 종류의 get요청이 올때 동작
app.get('*', (req, res) => {
res.send('Hello, get !!');
});
// post 요청 올때 동작
app.post('/', (req, res) => {
res.status(200).send('Hello, Post');
});
/* 5. 에러 처리 미들웨어를 만든다. */
app.use((err, req, res, next) => { // 에러 미들웨어는 인자는 반드시 4개 선언
console.error(err);
res
.status(500)
.send(err.message);
})
/* 6. 포트를 연결하여 서버를 실행한다. */
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 중.');
});
첫 번째 인수로 주소를 넣어주지 않는다면 미들웨어는 모든 요청에서 실행되고, 주소를 넣는다면 해당하는 요청에서만 실행된다.
아래와 같이 사용할 수 있다.
app.use(미들웨어) | 모든 요청에서 해당 미들웨어 실행 |
app.use('/path', 미들웨어) | path로 시작하는 요청에서 미들웨어 실행 |
app.post('/path', 미들웨어) | path로 시작하는 POST 요청에서 미들웨어 실행 |
에러 처리 미들웨어
...
app.get('/error', (req, res, next) => {
next(); // next()에 인수가 없다면, 바로 다음 미들웨어 함수로 넘어가게 된다.
}, (req, res) => {
try {
// .. 에러 발생 코드
} catch(err) {
error(err); // next()에 인수가 있다면, 에러 처리 미들웨어로 점프하게 된다.
}
});
...
// 에러 미들웨어는 인자는 반드시 4개 선언
app.use((err, req, res, next) => {
console.error(err);
res
.status(500)
.send(err.message);
})
위 코드를 보면 app.get에는 미들웨어(콜백함수)가 두 개 연결되어 있다.
next를 호출하면 바로 다음 미들웨어로 넘어갈 수 있다.
그리고 두 번째 미들웨어에서 catch로 에러를 잡으면, next() 함수 인자를 주면, 바로 에러 처리 미들웨어로 점프하게 된다.
에러 처리 미들웨어는 매개변수가 err, req, res, next로 네 개다.
모든 매개변수를 사용하지 않더라도 매개변수가 반드시 네 개여야 한다.
미들웨어 next()
위에서 본 봐와 같이,
next()를 하면 다음 미들웨어로 넘어가고, next(인수)를 하면 에러 핸들러로 넘어가게 된다.
또한, 한가지 특별한 기능이 있는데, next('route')를 하게 되면 다음 미들웨어가 아닌 다음 라우터로 넘어가게 된다.
...
app.get('/', (req, res, next) => {
if (false) {
next(); // 다음 미들웨어로 넘어간다.
} else {
next('route'); // next()에 'route'인수를 주게되면, 다음 미들웨어가 아닌, 다음 라우터로 넘어가게 된다.
}
}, (req, res) => {
// ... next()면 실행되지만,
// ... next('route')면 실행되지 않는다.
});
app.get('/', (req, res, next) => {
// ... next('route')면, 그 다음 라우터인 이쪽이 실행된다.
})
미들웨어 동작 원리
var express = require('express')
var bodyParser = require('body-parser')
var app = express();
app.use(bodyParser().json())
app.post('/profile', function(req, res) => {
console.log(req.body)
})
body-parser모듈을 불러오고 미들웨어 함수로서 사용되었다.
app.post()를 보니 req.body를 사용하는데, req객체에는 body라는 키는 원래 존재하지 않는다.
이 body라는 키와 밸류는 어디서 왔을까?
미들웨어의 특성들에 대해 알아보자.
미들웨어는 req, res, next를 매개변수로 가지는 함수 (에러 처리 미들웨어만 예외적으로 err, req, res, next) 로써 app.use나 app.get, app.post 등으로 장착한다.
또한, 아래와 같이 동시에 여러개의 미들웨어를 장착할 수도 있다.
app.use(
morgan('dev'),
express.static('/', path.join(__dirname, 'public')),
express.json(),
express.urlencoded({ extended: false }),
cookieParser(process.env.COOKIE_SECRET)
);
위 미들웨어들은 내부적으로 next를 호출하므로 연달아 쓸 수 있다.
next를 호출하지 않는 미들웨어는 res.send나 res.sendFile 등의 메서드로 응답을 보내야한다.
미들웨어 간 데이터를 전달하는 방법도 있다.
현재 요청이 처리되는 동안 req.data를 통해 미들웨어 간 데이터를 공유할 수 있으며, 새로운 요청이 올 시 req.data는 초기화된다.
속성명이 꼭 data일 필요는 없다. 마음대로 지으면 된다.
그러나 다른 미들웨어 속성과 겹치지 않게 조심하자. 가령 속성명 body는 body-parser 미들웨어와 기능이 겹칠 수 있다.
app.use(
(req, res, next) => {
req.data = 'new data...';
next(); // next하면 다음 미들웨어에 req객체가 인자로서 들어가게 된다.
},
(req, res, next) => {
// 따라서 위에서 추가한 키와 밸류를 사용할수 있게 된다.
console.log(req.data); // new data...
}
)
미들웨어 데이터 전달 방법을 이용해서 한번, 우리가 우리만의 미들웨어를 만들어보는 시간을 갖자.
// 사용자가 마음대로 만든 써드 파티 미들웨어
function thirdParty(req, res, next) {
const youtuber = "nomad";
console.log(`Hello ${youtuber}`);
req.youtbe = youtuber; // 미들웨어에서 사용자정의 객체 키와 벨류를 레퍼로 생성한다.
next(); // req 데이터를 저장한채 그대로 다음 미들웨어로 보낸다.
}
// 우리가 사용하는 미들웨어는 대부분 저런식으로 되어있다.
// 따라서 그냥 app.use에 미들웨어 메소드를 써놓고,
// 안의 내용물을 알지 못해도 특정 객체 인자를 다음 미들웨어에서 제약없이 쓸수 있는 이유이다.
app.use("/", thirdParty);
app.post("/login", (req, res) => {
// /login을 요청 하게 되면, 초기에 무조건 app.use가 실행되니 이 thirdParty미들웨어에서
// 어떠한 처리를 통해 결과값을 다음 미들웨어에 넘기게 되고, 우리는 req.youtube를 쓸수있게 된다.
console.log(req.youtube); // nomad
}
위와 같은 이론이라면,
app.use의 미들웨어에서 어떤 무언가의 처리가 되어 req.body라는 키와 밸류가 생성되고 next()가 된것을 알수 있다.
bodyParser().json()미들웨어에 의해서 생성된 객체를 다른 미들웨어 내에서 사용할 수 있는 이유이다.
Express 미들웨어 패키지 종류
const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const dotenv = require('dotenv');
const path = require('path');
dotenv.config();
const app = express();
app.set('port', process.env.PORT || 3000);
app.use(morgan('dev'));
app.use('/', express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false
},
name: 'session-cookie'
}));
app.use((req, res, next) => {
console.log('모든 요청에 다 실행됩니다.');
next();
})
app.get('/', (req, res, next) => {
console.log('GET / 요청에서만 실행됩니다.');
next();
}, (req, res) => {
throw new Error('에러는 에러 처리 미들웨어로 갑니다.');
});
app.use((err, req, res, next) => {
console.error(err);
res.status(500).send(err.message);
})
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 중.');
});
미들웨어 모듈
미들웨어 모듈 | 설명 | 내장 함수 |
body-parser | HTTP 요청 body를 파싱합니다. body, co-body, 그리고 raw-body도 참고하세요. | express.bodyParser |
compression | HTTP 요청들을 압축합니다. | express.compress |
connect-rid | 고유한 요청 ID를 생성합니다. | 없음 |
cookie-parser | 쿠키 헤더를 파싱하고 req.cookies에 할당합니다. cookies와 keygrip도 참고하세요. | express.cookieParser |
cookie-session | 쿠키 기반의 세션을 만듭니다. | express.cookieSession |
cors | 다양한 옵션들을 이용하여 Cross-origin resource sharing (CORS)를 활성화합니다. | 없음 |
csurf | CSRF 취약점을 방어합니다. | express.csrf |
errorhandler | 개발 중에 발생하는 에러를 핸들링하고 디버깅합니다. | express.errorHandler |
method-override | 헤더를 이용해 HTTP method를 덮어씁니다. | express.methodOverride |
morgan | HTTP 요청 로그를 남깁니다. | express.logger |
multer | multi-part 폼 데이터를 처리합니다. | express.bodyParser |
response-time | 응답 시간을 기록합니다. | express.responseTime |
serve-favicon | 파비콘을 제공합니다. | express.favicon |
serve-index | 주어진 경로의 디렉토리 리스트를 제공합니다. | express.directory |
serve-static | 정적 파일을 제공합니다. | express.static |
session | 서버 기반의 세션을 만듭니다 (개발 전용). | express.session |
timeout | HTTP 요청 처리를 위해 timeout을 만듭니다. | express.timeout |
vhost | 가상 도메인을 만듭니다. | express.vhost |
추가 미들웨어 외부 모듈
미들웨어 모듈 | 설명 |
connect-image-optimus | 이미지 제공을 최적화힙니다. 할 수 있다면 이미지를 .webp나 .jxr로 바꿉니다. |
express-debug | 템플릿 변수 (지역), 현재 세션, 기타 등등에 대한 정보를 제공하는 개발 도구입니다. |
express-partial-response | JSON 응답을 URL의 fields를 받아서 필터링합니다. Google API의 Partial Response를 활용합니다. |
express-simple-cdn | 정적 요소들을 위해 CDN을 사용하도록 도와줍니다. 다양한 호스트를 지원합니다. |
express-slash | 구현된 루터에 맟춰서 슬래쉬 유무를 맞춰줍니다. |
express-stormpath | 사용자 저장소, 인증 확인, 인증, SSO, 그리고 데이터 보안에 관련된 모듈입니다. (Okta로 합쳐졌습니다) |
express-uncapitalize | 대문자를 포함하는 HTTP 요청들을 표준 소문자 폼으로 리다이렉트시킵니다. containing uppercase to a canonical lowercase form. |
helmet | 다양한 HTTP 헤더를 설정해 앱이 안전하게 도와줍니다. |
join-io | 요청 횟수를 줄이기 위해 파일들을 묶어줍니다. |
passport | OAuth, OpenID 같은 방법들을 사용하는 인증 체계입니다. 자세한 정보는 http://passportjs.org/에서 확인하세요. |
static-expiry | 정적 에셋을 위해 헤더를 캐싱하거나 URL을 고유화합니다. |
view-helpers | 뷰 엔진들을 위한 일반적인 도움을 제공합니다. |
sriracha-admin | 동적으로 Mongoose의 관리 사이트를 만듭니다. |
Reference
https://expressjs.com/ko/guide/writing-middleware.html
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.