목차
미들웨어 기본 동작 방식 이해하기
Express 프레임워크에서 미들웨어는 요청(request)과 응답(response) 사이의 중간 지점에서 다양한 기능을 수행하는 함수입니다. 일반적으로 미들웨어는 정의된 순서대로 차례대로 실행됩니다. 즉, 첫 번째 미들웨어가 완료되면 다음 미들웨어로 제어가 넘어가고, 최종적으로 라우트 핸들러가 실행되어 응답을 보냅니다. 이러한 기본 흐름을 이해하는 것이 미들웨어 실행 순서 제어의 첫걸음입니다. 각 미들웨어 함수는 `next()` 함수를 호출하여 다음 미들웨어로 제어를 넘겨주는 역할을 합니다. 만약 `next()` 함수가 호출되지 않으면, 해당 미들웨어에서 요청 처리가 중단될 수 있으며, 이는 의도치 않은 동작을 야기할 수 있습니다. 따라서 미들웨어를 설계할 때는 항상 `next()` 함수의 호출 여부를 신중하게 고려해야 합니다.
Express 미들웨어는 크게 세 가지 종류로 나눌 수 있습니다. 애플리케이션 레벨 미들웨어, 라우터 레벨 미들웨어, 그리고 에러 핸들링 미들웨어입니다. 각 종류는 특정한 역할을 수행하며, 이들이 어떤 순서로 등록되고 호출되는지에 따라 전체 애플리케이션의 동작 방식이 달라집니다. 특히 `app.use()`나 `router.use()`와 같은 메서드를 사용하여 미들웨어를 등록할 때, 그 등록 순서가 곧 실행 순서가 된다는 점을 명심해야 합니다.
| 미들웨어 종류 | 주요 역할 | 실행 시점 |
|---|---|---|
| 애플리케이션 레벨 | 전역적인 기능 (로깅, 인증 등) | 모든 요청에 대해 등록된 순서대로 |
| 라우터 레벨 | 특정 라우트 그룹에 대한 기능 | 해당 라우터가 마운트된 이후 |
| 에러 핸들링 | 에러 발생 시 처리 | `next(err)` 호출 시 |

`next()` 함수를 활용한 흐름 제어
미들웨어의 실행 순서를 능동적으로 제어하는 가장 기본적인 방법은 `next()` 함수의 호출 방식을 조절하는 것입니다. `next()` 함수는 단순히 다음 미들웨어로 제어를 넘기는 것 외에도, 특정 조건에 따라 다른 미들웨어로 직접 건너뛰거나, 요청 처리를 완전히 중단하는 데 사용될 수 있습니다. 예를 들어, 인증 미들웨어에서 사용자가 인증되지 않았다면 `next()`를 호출하지 않고 바로 에러 응답을 보낼 수 있습니다. 반대로, 특정 경로에만 적용될 미들웨어를 건너뛰고 싶다면, 조건문 안에서 `next()`를 호출하여 원하는 미들웨어로 제어를 이동시킬 수 있습니다.
또한, `next('route')`라는 특별한 인자를 사용하여 현재 라우터의 나머지 미들웨어를 건너뛰고 다음 라우트로 바로 넘어갈 수도 있습니다. 이는 특정 조건에서만 라우트 핸들러가 실행되도록 제어할 때 유용합니다. 이를 통해 불필요한 연산을 방지하고 애플리케이션의 효율성을 높일 수 있습니다. `next()` 함수의 올바른 사용법을 익히는 것은 Express 개발의 필수 요소라고 할 수 있습니다.
▶ `next()`: 다음 미들웨어 또는 라우트 핸들러로 제어 이동
▶ `next(err)`: 에러 핸들링 미들웨어로 제어 이동
▶ `next('route')`: 현재 라우터의 나머지 미들웨어를 건너뛰고 다음 라우트로 이동

다양한 미들웨어 활용 전략
Express 미들웨어의 실행 순서를 제어하는 능력은 애플리케이션의 구조를 유연하게 만들고, 복잡한 요구사항을 효과적으로 처리할 수 있게 해줍니다. 예를 들어, 모든 요청에 대해 공통적으로 로깅을 수행하는 미들웨어를 가장 먼저 등록하고, 그 다음으로 사용자 인증을 위한 미들웨어를 등록할 수 있습니다. 만약 인증에 실패하면, 인증 미들웨어에서 즉시 응답을 보내고 `next()`를 호출하지 않음으로써 이후의 라우트 핸들러 실행을 막을 수 있습니다.
또한, 특정 API 엔드포인트 그룹에만 적용되는 미들웨어를 별도의 라우터로 분리하고, 해당 라우터를 `app.use('/api', apiRouter)`와 같이 등록하여 관리할 수 있습니다. 이 경우 `apiRouter`에 등록된 미들웨어들은 `/api` 경로로 들어오는 모든 요청에 대해 순서대로 실행됩니다. 커스텀 미들웨어를 작성하여 비즈니스 로직을 분리하고 재사용성을 높이는 것도 좋은 전략입니다. 예를 들어, 요청 본문에서 특정 파라미터의 유효성을 검증하는 미들웨어를 만들고, 이를 여러 라우트에서 공통적으로 사용할 수 있습니다.
핵심 포인트: 각 미들웨어는 명확한 책임 범위를 가지도록 설계하고, `next()` 함수를 통한 제어 흐름을 신중하게 관리하는 것이 Express 애플리케이션의 안정성과 유지보수성을 높이는 열쇠입니다.
| 활용 시나리오 | 미들웨어 실행 순서 예시 | 주요 목적 |
|---|---|---|
| 공통 로깅 및 인증 | `app.use(loggingMiddleware);` `app.use(authMiddleware);` |
모든 요청에 대한 기본 보안 및 추적 |
| API 경로별 기능 분리 | `app.use('/api', apiRouter);` `apiRouter.get('/users', userController);` |
모듈화 및 코드 관리 용이성 증대 |
| 특정 조건에서의 흐름 제어 | if (condition) { next(); } else { res.send('Denied'); } | 정교한 요청 처리 로직 구현 |

미들웨어 호출 방식 이해하기
Express 애플리케이션에서 미들웨어는 요청(request)과 응답(response) 사이에서 다양한 기능을 수행하는 함수들의 연속입니다. 이러한 미들웨어들은 일반적으로 정의된 순서대로 실행됩니다. 하지만 때로는 특정 미들웨어를 먼저 실행하거나, 건너뛰거나, 특정 조건에서만 실행되도록 제어해야 할 필요가 있습니다. Express는 이러한 미들웨어 실행 흐름을 유연하게 관리할 수 있는 다양한 방법을 제공합니다. 각 미들웨어가 어떻게 호출되고, 이 호출 방식을 어떻게 활용하여 원하는 순서대로 실행되게 만드는지 이해하는 것이 중요합니다. 미들웨어 함수는 세 개의 인자, 즉 요청 객체(req), 응답 객체(res), 그리고 다음 미들웨어를 호출하는 함수(next)를 받습니다. `next()` 함수를 호출하는 것은 현재 미들웨어 처리가 완료되었으며, 다음 미들웨어로 제어권을 넘기겠다는 의미입니다. 만약 `next()` 함수가 호출되지 않으면, 미들웨어 체인은 그 지점에서 멈추게 됩니다. 이는 라우팅이나 특정 응답을 바로 보내는 경우에 유용하게 사용될 수 있습니다.
| 호출 방식 | 설명 |
|---|---|
| app.use(middleware) | 모든 요청에 대해 순서대로 미들웨어를 실행합니다. |
| app.get(path, middleware) | GET 메소드와 특정 경로에 대해 미들웨어를 실행합니다. |
| router.use(middleware) | 특정 라우터 인스턴스에 속한 모든 요청에 대해 미들웨어를 실행합니다. |
next() 함수의 다양한 활용
앞서 언급했듯이 `next()` 함수는 미들웨어 체인을 계속 진행시키는 핵심적인 역할을 합니다. 하지만 `next()` 함수를 어떻게 사용하느냐에 따라 미들웨어 실행 순서를 동적으로 제어할 수 있습니다. 가장 기본적인 사용법은 인자 없이 `next()`를 호출하여 단순히 다음 미들웨어를 실행시키는 것입니다. 그러나 `next()` 함수에 경로를 인자로 전달하면, Express는 해당 경로로 요청을 다시 보낼 수 있습니다. 이는 마치 사용자가 직접 해당 경로로 요청을 보낸 것처럼 동작하며, 미들웨어 체인을 건너뛰거나 특정 미들웨어들을 반복적으로 실행시킬 때 유용합니다. 예를 들어, 인증 미들웨어에서 `next('/login')`을 호출하면, 인증에 실패한 사용자를 로그인 페이지로 즉시 리디렉션하는 효과를 얻을 수 있습니다. 또한, `next(err)`와 같이 에러 객체를 전달하면, Express의 내장 에러 핸들링 미들웨어나 개발자가 정의한 에러 핸들링 미들웨어로 제어권을 넘기게 됩니다. 이는 예외 처리를 체계적으로 관리하는 데 필수적인 기능입니다. 이러한 `next()` 함수의 다양한 활용법을 숙지하면, Express 애플리케이션의 복잡한 요청 처리 흐름을 보다 정교하게 설계할 수 있습니다.
핵심 포인트: `next()` 함수에 경로를 인자로 전달하면, 현재 요청을 해당 경로로 다시 보내는 효과를 낼 수 있어 복잡한 라우팅 제어가 가능해집니다.
▶ next() 기본 호출: 현재 미들웨어 실행 완료 후 다음 미들웨어로 이동
▶ next('/path'): 지정된 경로로 요청을 다시 보내고 해당 경로의 미들웨어부터 실행
▶ next(error): 에러 핸들링 미들웨어로 제어권 전달
핵심 요약
• Express 미들웨어는 정의된 순서대로 실행되며, `next()` 함수로 제어권을 넘깁니다.
• `next()` 함수에 경로를 인자로 전달하여 미들웨어 실행 순서를 동적으로 제어할 수 있습니다.
• `next(error)`를 통해 에러 핸들링 메커니즘을 효과적으로 활용할 수 있습니다.
주요 질문 FAQ
Q. Express 미들웨어는 어떤 순서로 실행되나요?
Express 애플리케이션에서 미들웨어는 등록된 순서대로 실행됩니다. 즉, app.use()나 라우터.use() 등으로 미들웨어를 등록할 때 가장 먼저 등록된 미들웨어가 가장 먼저 호출되며, 이후 순차적으로 실행됩니다. 만약 특정 조건에 따라 미들웨어 실행을 건너뛰거나, 여러 미들웨어를 병렬적으로 실행하고 싶다면 next() 함수 호출 방식이나 미들웨어 함수의 조건부 로직을 활용해야 합니다.
Q. 라우터 미들웨어의 실행 순서 제어는 어떻게 할 수 있나요?
Express에서는 라우터(Router) 객체를 사용하여 여러 라우트를 그룹화하고 관리할 수 있습니다. 라우터 역시 미들웨어처럼 등록 순서대로 실행되므로, 라우터 객체를 app.use()로 등록할 때 그 순서를 잘 고려해야 합니다. 예를 들어, 공통 로깅 미들웨어를 먼저 등록하고, 그 뒤에 특정 라우터 그룹을 등록하는 방식입니다. 또한, 라우터 내에서도 개별 라우트 핸들러들이 정의된 순서대로 실행됩니다.
Q. 특정 요청에 대해서만 미들웨어를 실행하고 싶다면 어떻게 해야 하나요?
이는 미들웨어 함수 내에서 조건문을 사용하여 구현할 수 있습니다. 예를 들어, 요청 URL의 특정 경로('/admin'과 같은)에 대해서만 인증 미들웨어를 실행하고 싶다면, 미들웨어 함수 안에서 req.originalUrl이나 req.path를 확인하여 해당 경로일 때만 로직을 수행하고 next()를 호출하도록 할 수 있습니다. 그렇지 않은 경우에는 바로 next()를 호출하여 다음 미들웨어나 라우터로 넘어가게 합니다.
Q. 미들웨어에서 next() 함수는 언제 호출해야 하나요?
next() 함수는 현재 미들웨어의 작업이 완료되었고, 다음 미들웨어나 라우트 핸들러로 제어를 넘겨야 할 때 호출합니다. 만약 next()를 호출하지 않으면 요청 처리가 더 이상 진행되지 않아 응답이 보내지지 않습니다. 반대로, 응답을 이미 보낸 상태(res.send(), res.json() 등)에서는 더 이상 next()를 호출해서는 안 됩니다. 오류 처리를 위한 next(err)와 같이 인자를 넘겨서 호출할 수도 있습니다.
Q. 에러 핸들링 미들웨어는 어디에 위치해야 실행 순서가 맞나요?
에러 핸들링 미들웨어는 반드시 모든 다른 미들웨어 및 라우트 핸들러 뒤에 등록되어야 합니다. Express는 미들웨어를 순서대로 처리하기 때문에, 에러 핸들링 미들웨어가 앞에 있으면 일반 요청을 처리하는 다른 미들웨어들이 실행되지 못합니다. 따라서, 모든 비즈니스 로직 처리가 끝난 후, 발생할 수 있는 오류를 잡기 위해 마지막에 등록하는 것이 일반적입니다.
Q. 미들웨어를 그룹화하여 실행 순서를 관리하는 좋은 방법이 있을까요?
Express의 Router 객체를 활용하는 것이 매우 효과적입니다. 관련된 라우트들을 하나의 Router 인스턴스에 묶고, 이 Router 객체를 `app.use('/prefix', myRoutes)`와 같이 등록할 수 있습니다. 이를 통해 URL 경로별로 미들웨어 스택을 구성할 수 있습니다. 예를 들어, `/api` 경로로 들어오는 요청에만 적용될 인증 미들웨어를 해당 라우터에 등록하고, `/api/users`나 `/api/posts`와 같은 하위 라우트들을 이 라우터 안에 정의하여 체계적으로 관리할 수 있습니다.
Q. 미들웨어 실행 순서에 따라 요청 객체(req)나 응답 객체(res)에 데이터를 추가하거나 수정할 수 있나요?
네, 가능합니다. 미들웨어는 `req`와 `res` 객체를 수정하거나 새로운 속성을 추가할 수 있습니다. 예를 들어, 인증 미들웨어에서 사용자 정보를 파싱하여 `req.user`에 저장하면, 이후의 미들웨어나 라우트 핸들러에서 `req.user`에 접근하여 해당 사용자 정보를 활용할 수 있습니다. 이러한 방식으로 미들웨어 간에 데이터를 공유하고 요청 처리 과정을 효율적으로 구성할 수 있습니다.
Q. 미들웨어를 사용하여 요청의 특정 부분을 건너뛰고 싶을 때 주의해야 할 점은 무엇인가요?
가장 중요한 것은 `next()` 함수를 올바르게 호출하는 것입니다. 특정 미들웨어를 건너뛰기 위해 `if` 문 등을 사용하여 `next()` 호출을 조건부로 만드는 것은 가능하지만, 만약 해당 조건을 만족하지 못해서 `next()`가 호출되지 않으면 요청 처리가 더 이상 진행되지 않는다는 점을 명심해야 합니다. 또한, 의도치 않게 중요한 미들웨어를 건너뛰지 않도록 코드 흐름을 면밀히 검토해야 합니다. 때로는 `next('router')`와 같이 Express의 라우팅 메커니즘에 특정 명령을 내리는 것을 고려할 수도 있습니다.