← 개인 공부 · Next/Express
STUDY NOTE · 개념 정리

Express 미들웨어 정리: 실행 순서, next(), 에러 처리 미들웨어

Next/Express
Express 앱은 결국 요청이 차례로 통과하는 함수(미들웨어)들의 줄이다. 각 미들웨어는 (req, res, next)를 받아 응답을 끝내거나, next()로 다음에 넘긴다. 등록한 순서가 곧 실행 순서이고, 인자가 4개인 미들웨어는 에러만 받는다.

1. 미들웨어의 모양

// 일반 미들웨어
app.use((req, res, next) => {
req.startedAt = Date.now();
next(); // 다음 미들웨어로
});

// 라우트 핸들러도 미들웨어의 한 종류 (보통 응답으로 끝냄)
app.get("/api/posts", (req, res) => {
res.json(posts);
});

// 에러 처리 미들웨어: 인자가 반드시 4개
app.use((err, req, res, next) => {
res.status(err.status ?? 500).json({ code: err.code ?? "INTERNAL" });
});
미들웨어가 할 수 있는 일예
req·res 고치기파싱한 본문을 req.body에, 로그인 사용자를 req.user에
응답으로 끝내기res.json(), res.status(401).end()
다음으로 넘기기next()
에러로 넘기기next(err) → 일반 미들웨어는 건너뛰고 에러 미들웨어로
남은 라우트 미들웨어 건너뛰기next("route") (app.get 등 라우트 메서드에서만)

2. 실행 순서

등록한 순서대로, 경로가 맞는 것만 실행된다. 그래서 순서가 곧 정책이다.

app.use(helmet()); // 1. 보안 헤더
app.use(express.json({ limit: "1mb" })); // 2. 본문 파싱 (이게 없으면 req.body가 undefined)
app.use(requestLog); // 3. 로그
app.use("/api", rateLimit); // 4. /api로 시작할 때만
app.use("/api/admin", requireAdmin); // 5. 관리자 확인
app.use("/api", apiRouter); // 6. 라우터
app.use((req, res) => res.status(404).json({ code: "NOT_FOUND" })); // 7. 아무것도 안 맞으면
app.use(errorHandler); // 8. 에러는 맨 마지막
실수결과
인증 미들웨어를 라우터 뒤에 등록이미 응답이 나가서 인증이 안 걸림
express.json()을 라우터 뒤에핸들러에서 req.body가 비어 있음
에러 미들웨어를 앞에 등록뒤에서 난 에러를 못 받음
next()도 안 하고 응답도 안 함요청이 끝나지 않고 타임아웃까지 매달림
응답 후 next() 또 호출다음 미들웨어가 또 응답하려다 "Cannot set headers after they are sent"

3. 미들웨어의 범위

종류등록적용 범위
애플리케이션 수준app.use(fn), app.use("/path", fn)모든 요청, 또는 경로가 그 접두사로 시작하는 요청
라우터 수준router.use(fn)그 라우터에 들어온 요청만
라우트 수준app.get("/x", auth, handler)그 라우트만 (여러 개를 줄줄이 넣을 수 있음)
내장express.json(), express.static()-
서드파티cors, helmet, morgan, cookie-parser-

app.use("/api", fn) 안에서 req.path는 /api를 뗀 나머지다. 원래 경로가 필요하면 req.originalUrl을 본다.

4. 에러 처리와 비동기

동기 코드에서 throw하면 Express가 잡아서 에러 미들웨어로 보낸다. Express 4에서는 async 함수가 reject돼도 잡지 못해서 직접 next(err)를 불러야 했다.

// Express 4: 감싸는 헬퍼가 필요했음
const wrap = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
app.get("/posts/:id", wrap(async (req, res) => {
res.json(await Post.findById(req.params.id));
}));

Express 5부터는 핸들러가 돌려준 Promise가 reject되면 자동으로 next(err)로 넘긴다. 그래서 async 핸들러에서 그냥 throw해도 된다.

에러 응답 원칙:

  1. 클라이언트에는 정해진 코드와 메시지만, 스택이나 내부 메시지는 서버 로그에만.
  2. 알고 던진 에러(400, 401, 404)와 예상 못 한 에러(500)를 클래스로 구분해 두면 에러 미들웨어가 단순해진다.

5. 미들웨어 vs 다른 프레임워크의 같은 개념

프레임워크비슷한 개념차이
SpringFilter(서블릿 단), Interceptor(핸들러 전후), AOP역할별로 층이 나뉘어 있음
Next.jsmiddleware.ts (Edge에서 요청 전)라우트 앞 한 단계, 응답 가공보다는 리다이렉트·헤더용
Koaasync 미들웨어, await next()다음 미들웨어가 끝난 뒤 돌아와서 후처리하는 '양파' 모델이 기본

Express도 res.on("finish", ...)를 걸면 응답이 끝난 뒤 시간을 잴 수 있다. 응답 시간 로깅을 이렇게 만든다.

6. 정리

  1. 미들웨어 = (req, res, next). 응답으로 끝내거나 next()로 넘기거나, 둘 중 하나는 꼭.
  2. 등록 순서가 실행 순서: 보안 → 파싱 → 로그 → 제한 → 인증 → 라우터 → 404 → 에러.
  3. 에러 미들웨어는 인자 4개, 맨 마지막.
  4. Express 4는 async 에러를 직접 넘겨야 하고, Express 5는 자동으로 넘긴다.
Gunmo Lee