미들웨어란 요청 오브젝트(req), 응답 오브젝트(res), 그리고 요청-응답 주기 중 그 다음 미들웨어 함수에 접근 권한을 갖는 함수이다.
쉽게 말하면 클라이언트(브라우저, 사용자)에게 요청이 오고 그 요청에 대한 응답을 보내려는 중간(미들)에서 목적에 맞게 처리하는 함수이다.
이는 원하는 만큼 여러개 만들어 적용할 수도 있다. 예시 : app.get("/", middleware1, middleware2, handleHome);
상황에 따라 요청에 응답하여 요청-응답 주기를 종료시킬 수도 있고, 응답하지 않고 next()
를 호출해 다음 함수로 넘어갈 수도 있다.
현재 미들웨어 함수에서 응답하지 않을 경우, 꼭 next()를 호출하여 다음 미들웨어 함수에 제어를 전달해야한다. 그렇지 않으면 해당 요청은 정지된 채로 방치된다.
여기서
next()
가 호출하는 다음 함수는 뭘까?
예를 들어, app.get("/login",auth,handleLogin);
의 경우 auth가 미들웨어이고, auth에서 next()를 호출하게 되면 기존의 req, res 객체를 가지고 handleLogin을 실행하게 된다. 보다시피 왼쪽에서 오른쪽으로 순차적으로 진행되므로 사용시 순서에 주의하자.
모든 라우트 핸들러(=컨트롤러)는 미들웨어가 될 수 있다. controller는 세 개의 argument를 가지고 있다. (req, res, next)
컨트롤러 내부에서 next()를 호출한다면 다음 함수로 이어주는 미들웨어가 되는 것이고, next()
를 호출하지 않는다면 finalware이 된다. finalware는 next가 불필요하므로 매개변수 작성시 생략해도 된다.(req, res)
이는 app.METHOD()
함수(get, post,...) 및 app.use()
를 이용해 앱 오브젝트의 인스턴스에 바인드된 미들웨어를 말한다.
아래 예시를 보면 myLogger가 애플리케이션 레벨의 미들웨어이고, handleHome이 일반 컨트롤러(finalware)가 된다.
app.METHOD()
함수(여기선 get)를 이용한 미들웨어const myLogger = (req, res, next) => {
console.log("I'm in the middle!");
next();
}
const handleHome = (req, res, next) => { //보통 next를 생략하고 (req,res)=>{}로 사용함
return res.end();
};
app.get("/", myLogger, handleHome);
하지만 app.get("/", myLogger, handleHome);
이렇게 사용하면 해당 미들웨어는 '/' URL에만 적용이 되므로 큰 쓸모는 없다.
app.use()
를 이용한 미들웨어 (예시_ myLogger)app.use()
를 사용하면 여러 URL(route)혹은 특정 URL의 모든 유형의 HTTP요청에 적용되는 글로벌한 미들웨어를 만들 수 있다.
❗ 주의: 코드 작성시 app.use()
가 app.get()
보다 와야한다.
미들웨어는 매우 다양한 용도로 사용될 수 있지만, 아래와 같이 사용자의 authentication을 인증하기 위해서나 HTTP request를 로그하는데 자주 사용된다.
// GET http://localhost:4000/ 과 같이 출력되도록 하는 미들웨어
const myLogger= (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
}
// 로그인한 유저인지 확인해주는 미들웨어
const auth = (req, res, next) => {
사용자 검증 로직...
if (!검증결과) {
return res.send("<h1>Go away!</h1>"); //그 다음 함수로 못넘어가고 응답 종료
}
console.log("Continue..");
next(); //인증된 사용자임을 확인하고 다음 함수로 넘어가게 해줌
}
const handleHome = (req, res) => { // 파이널웨어 -> 보통 next 생략
return res.send("Hello, world!");
};
const handlePrivate = (req, res) => { // 파이널웨어
return res.send("Welcome to the private lounge.");
}
app.use(myLogger);
//마운트 경로가 없이 설정된 미들웨어로, 모든 경로의 모든 요청을 수신할 때마다 실행된다.
app.get("/", handleHome);
//http://localhost:4000/에 GET요청을 보내면 myLogger → handleHome 순으로 실행된다.
app.get("/protected", auth, handlePrivate);
//http://localhost:4000/protected에 GET요청을 보내면 myLogger → auth → handlePrivate 순으로 실행된다.
이처럼 특정 경로 없이 설정된 미들웨어는 사용하면 하나의 URL에만 적용되는 것이 아니라, 모든 경로에서 각각의 라우트 핸들러보다 먼저 실행되어 거쳐가는 것을 알 수 있다.
참고로 하단의 예시 1, 2는 서로 같다.
예시 1
app.use(myLogger);
app.get("/", handleHome);
app.get("/protected", handlePrivate);
예시 2
app.get("/", logger, handleHome);
app.get("/protected", logger, handleProtected);
만약app.use()
사용시 마운트 경로를 설정했다면(app.use(경로, 미들웨어)
), 해당 경로에 대한 모든 유형의 HTTP 요청에 대해 실행된다.
예시
app.use('/user/:id', myLogger);
app.get('/user/:id',getUser);
, app.post('/user/:id',postUser);
등과 같이 해당 URL에 대한 모든 요청에 myLogger가 먼저 실행된다.
이는 express.Router()
인스턴스에 바인드된다는 점을 제외하면 앞서 살펴본 애플리케이션 레벨 미들웨어와 동일한 방식으로 작동한다.
const app = express();
const userRouter = express.Router();
const myLogger= (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
}
const getUserInfo=(req,res)=>{
...
res.json({success:true,user:{name:"noma",age:24}});
}
const postSettings=(req,res)=>{
...
res.send("변경 완료");
}
//localhost:4000/user에 오는 모든 요청에 myLogger 미들웨어 적용
// 즉, /user로 시작되는 모든 경로에 적용된다.
userRouter.use(myLogger);
//만약 localhost:4000/users/noma98에 GET 요청이 오면 myLogger → getUserInfo 순으로 실행됨
userRouter.get('/:id', getUserInfo);
//localhost:4000/users/settings에 POST 요청이 오면 myLogger → postUserInfo 순으로 실행됨
userRouter.post('/settings', postUserInfo);
app.use('/user', userRouter);
말그대로 express에서 기본적으로 제공하는 미들웨어이다. 이전에 Express에 포함되었던 다양한 미들웨어 함수들은 별도의 모듈로 분리되었고, 현재 유일한 기본 제공 미들웨어 함수에는 express.static()
이 있다.
express.static(root, [options])
이 함수는 serve-static을 기반으로 하며, Express 애플리케이션의 정적 자산을 제공하는 역할을 한다. 자세한 것은 공식문서를 참고하도록 하자.
Express 앱에 여러 기능을 추가하기 위해 사용되는 외부 미들웨어로, 필요한 모듈을 npm i 로 설치 후 app.use()
로 사용하면 된다.
morgan은 nodeJS용 request logger middleware이다. 각종 Request 요청에 대한 간략한 정보들을 콘솔에 출력해주는 역할을 한다.
$ npm i morgan -D
(morgan은 앱을 작동시키는데 꼭 필요한 요소가 아니므로 devDependencies 옵션을 사용해 설치)morgan()
함수 호출를 호출하면 설정된 옵션(dev, combined, common, short, tiny)에 따라 미들웨어를 리턴해준다.app.use()
에 넣기import morgan from "morgan";
app.use(morgan("dev"));
다양한 옵션이 존재하지만 method(GET,...), path(/, /login,..), status code(200,404,..), 응답 시간(11.7ms) 등의 정보를 가지고 있는 "dev"를 주로 사용한다.
그러면 아래와 같이 서버가 요청을 받을 때마다 콘솔에 출력되는 걸 볼 수 있다.
에러 핸들링을 하는 미들웨어는 말그대로 에러를 다루기 위한 미들웨어로, 4개의 인자를 사용해서 정의하도록 한다. (err, req, res, next)
이는 아직 사용해본 적이 없으므로 다음에 다시 다뤄보도록 하겠다.