Express | Middleware의 개념과 유형, 사용법 정리 (+morgan)

Noma·2021년 8월 22일
2

1. 미들웨어(Middleware)란?

미들웨어란 요청 오브젝트(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)

2. Express에서의 미들웨어 유형

  • 애플리케이션 레벨 미들웨어
  • 라우터 레벨 미들웨어
  • 기본 제공 미들웨어
  • 써드파티 미들웨어
  • 오류 처리 미들웨어

2.1 애플리케이션 레벨 미들웨어

이는 app.METHOD()함수(get, post,...) 및 app.use()를 이용해 앱 오브젝트의 인스턴스에 바인드된 미들웨어를 말한다.

아래 예시를 보면 myLogger가 애플리케이션 레벨의 미들웨어이고, handleHome이 일반 컨트롤러(finalware)가 된다.

1. 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에만 적용이 되므로 큰 쓸모는 없다.

2. 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가 먼저 실행된다.

2.2 라우터 레벨 미들웨어

이는 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);

2.3 기본 제공 미들웨어

말그대로 express에서 기본적으로 제공하는 미들웨어이다. 이전에 Express에 포함되었던 다양한 미들웨어 함수들은 별도의 모듈로 분리되었고, 현재 유일한 기본 제공 미들웨어 함수에는 express.static()이 있다.

express.static(root, [options])

이 함수는 serve-static을 기반으로 하며, Express 애플리케이션의 정적 자산을 제공하는 역할을 한다. 자세한 것은 공식문서를 참고하도록 하자.

2.4 써드파티 미들웨어

Express 앱에 여러 기능을 추가하기 위해 사용되는 외부 미들웨어로, 필요한 모듈을 npm i 로 설치 후 app.use()로 사용하면 된다.

예시 : morgan 사용해보기

morgan은 nodeJS용 request logger middleware이다. 각종 Request 요청에 대한 간략한 정보들을 콘솔에 출력해주는 역할을 한다.

  1. $ npm i morgan -D (morgan은 앱을 작동시키는데 꼭 필요한 요소가 아니므로 devDependencies 옵션을 사용해 설치)
  2. morgan() 함수 호출를 호출하면 설정된 옵션(dev, combined, common, short, tiny)에 따라 미들웨어를 리턴해준다.
  3. 리턴된 미들웨어를 app.use()에 넣기
import morgan from "morgan";
app.use(morgan("dev")); 

다양한 옵션이 존재하지만 method(GET,...), path(/, /login,..), status code(200,404,..), 응답 시간(11.7ms) 등의 정보를 가지고 있는 "dev"를 주로 사용한다.

그러면 아래와 같이 서버가 요청을 받을 때마다 콘솔에 출력되는 걸 볼 수 있다.

2.5 에러 핸들링 미들웨어

에러 핸들링을 하는 미들웨어는 말그대로 에러를 다루기 위한 미들웨어로, 4개의 인자를 사용해서 정의하도록 한다. (err, req, res, next)

이는 아직 사용해본 적이 없으므로 다음에 다시 다뤄보도록 하겠다.

profile
Frontend Web/App Engineer

0개의 댓글