Express 미들웨어 파이프라인 해부
Express 요청 흐름, 미들웨어 역할 분리, next와 에러 핸들러 위치를 정리
Posted
5️⃣
By okorion
Express 미들웨어 파이프라인 해부
결론 요약
- Express는 라우팅 라이브러리가 아니라 요청(Request)을 처리하는 파이프라인 관리자다.
- 핵심은
req → middleware chain → res흐름이며, 순서(order)가 동작을 결정한다. - 미들웨어는 전처리·라우팅·에러 처리의 역할 분리가 생명이다.
next()는 “다음으로 넘긴다”가 아니라 제어권을 명시적으로 전달하는 계약이다.- 404와 에러 핸들러는 위치가 틀리면 절대 동작하지 않는다.
Express.js의 본질: 요청 파이프라인과 미들웨어
1️⃣ 정의: Express는 무엇을 관리하는가
Express는 HTTP 서버 위에 얹힌 요청 처리 파이프라인 관리자다. 각 요청은 등록된 미들웨어를 위에서 아래로 순차 통과하며, 응답이 전송되는 순간 파이프라인은 종료된다.
핵심 관점 전환 ❌ “URL → 함수 매핑” ✅ “요청이 흐르는 파이프에 어떤 필터를 어떤 순서로 끼울 것인가”
2️⃣ 왜 필요한가: if/else 서버의 붕괴를 막기 위해
Node의 http.createServer()로도 서버는 만들 수 있다. 문제는 요청 조건이 늘어날수록 제어 흐름이 붕괴한다는 점이다.
- 인증 여부
- 바디 파싱
- 로깅
- 권한 체크
- 에러 처리
Express는 이들을 “순서 있는 단계”로 분리하게 강제한다. 이 강제가 없으면, 코드 규모가 커질수록 버그는 구조적으로 늘어난다.
3️⃣ 어떻게 동작하는가: req → middleware chain → res
요청 처리 흐름(단계별)
- 클라이언트 요청 수신
app.use / app.get / app.post등으로 등록된 미들웨어를 등록 순서대로 실행각 미들웨어는 세 가지 중 하나를 선택
res.send / res.json / res.end→ 응답 종료next()→ 다음 미들웨어로 전달next(err)→ 에러 핸들러로 점프
- 응답이 전송되면 파이프라인 종료
이 구조 때문에 “순서”는 로직 그 자체다.
4️⃣ 미들웨어의 3가지 역할 (섞이면 바로 망가진다)
① 전처리 미들웨어 (Pre-processing)
요청을 “사용 가능한 상태”로 만든다.
- 바디 파싱 (
express.json) - 인증/권한
- 로깅
- request id 부여
1
2
app.use(express.json());
app.use(authMiddleware);
특징
- 대부분
app.use - 요청을 변경(req 확장)하지만, 응답은 보내지 않는다
② 라우팅 미들웨어 (Routing)
요청을 의미 있는 작업 단위로 분기한다.
1
app.get('/products/:id', productController.getProduct);
특징
- URL + HTTP method 기준
- 비즈니스 로직의 “입구” 역할
③ 에러 처리 미들웨어 (Error handling)
정상 흐름과 완전히 다른 파이프라인이다.
1
2
3
app.use((err, req, res, next) => {
res.status(500).json({ message: err.message });
});
특징
- 인자가 4개
(err, req, res, next) - 반드시 모든 라우트 뒤에 위치해야 한다
5️⃣ next()의 의미와 흔한 버그
next()의 정확한 의미
- “다음 미들웨어를 실행하라”
- 호출하지 않으면 요청은 거기서 멈춘다
흔한 버그 3종
❌ 1. next() 누락 → 무한 대기
1
2
3
4
5
6
app.use((req, res, next) => {
if (!req.user) {
res.status(401).end();
}
// next() 없음 → 인증된 요청도 멈춤
});
❌ 2. 응답 후 next() 호출 → 응답 중복
1
2
res.json(data);
next(); // 이미 응답했는데 다음으로 전달
❌ 3. 에러를 던지고 next(err) 안 씀
1
throw new Error('fail'); // async context에서 누락 가능
✅ 해결 패턴
- 응답을 보냈으면 return
- 에러는
next(err)로 위임 - 미들웨어 하나 = 책임 하나
6️⃣ 라우터 분리: Express Router 설계 원칙
결론
라우터는 “파일 분리”가 아니라 도메인 경계 선언이다.
기본 원칙
- URL prefix = 도메인
- Router 내부에서는 상대 경로만 사용
- 컨트롤러는 라우터 밖
1
2
3
4
5
// app.js
app.use('/products', productRoutes);
// routes/products.js
router.get('/:id', controller.getProduct);
라우터가 책임지면 안 되는 것
- DB 접근
- 검증 로직
- 인증 판단
→ 라우터는 파이프 연결자, 로직은 미들웨어/컨트롤러로 이동
7️⃣ 404 처리와 에러 핸들러의 위치 규칙
404는 “모든 라우트를 통과한 뒤”에만 의미가 있다
1
2
3
app.use((req, res) => {
res.status(404).send('Not Found');
});
에러 핸들러 위치 규칙
1
2
3
4
[전처리]
[라우트]
[404]
[에러 핸들러] ← 반드시 맨 마지막
이유
- Express는 위에서 아래로만 탐색
- 에러 핸들러는 “최후의 안전망”
8️⃣ 나쁜 예 vs 개선 예
❌ 나쁜 예: 모든 것이 라우트에 섞임
1
2
3
4
5
app.post('/order', (req, res) => {
if (!req.body.user) return res.status(400).end();
db.save(req.body);
res.json({ ok: true });
});
문제:
- 검증, 비즈니스, 응답이 한 덩어리
- 테스트/재사용 불가
✅ 개선 예: 미들웨어 분리
1
2
3
4
5
router.post(
'/',
validateOrder,
orderController.createOrder
);
- 검증/로직/응답 분리
- 파이프라인이 읽힘
Express 구조 템플릿 (권장)
1
2
3
4
5
6
7
8
9
10
11
src/
├─ app.js
├─ routes/
│ ├─ products.js
│ └─ orders.js
├─ controllers/
├─ middleware/
│ ├─ auth.js
│ ├─ validate.js
│ └─ error.js
└─ services/
실무 체크리스트 (재학습용)
- Express를 “요청 파이프라인”으로 설명할 수 있다
- 미들웨어 3종(전처리/라우팅/에러)을 구분해 쓸 수 있다
next()누락/중복 호출의 위험을 알고 있다- 라우터는 도메인 경계만 책임진다
- 404는 모든 라우트 뒤에 있다
- 에러 핸들러는 파일·순서가 보장된다
- 응답 후에는 반드시 return 한다
- 미들웨어 하나에 책임 하나만 있다
This post is licensed under CC BY 4.0 by the author.
