[Koa] Middleware (Onion Model)
정의
Koa Middleware 는 요청/응답 사이에 실행되는 async 함수입니다. 각 미들웨어는 ctx (context) 와 next (다음 미들웨어를 실행하는 함수) 를 받고, await next() 로 downstream 을 실행하고 그 이후에 upstream 로직을 실행합니다. 이것이 onion model (양파 모델).
기본 구조
app.use(async (ctx, next) => {
console.log('before');
await next();
console.log('after');
});
next()를 호출 안 하면 다음 미들웨어로 안 감 (chain 중단)await next()를 잊으면 async 흐름 깨짐
Onion Model 예시
app.use(async (ctx, next) => {
console.log('1 in');
await next();
console.log('1 out');
});
app.use(async (ctx, next) => {
console.log('2 in');
await next();
console.log('2 out');
});
app.use(async (ctx) => {
console.log('3 handler');
ctx.body = 'response';
});
실행 순서:
1 in
2 in
3 handler ← 응답 body 세팅
2 out
1 out ← 마지막
await next()이전: downstream 방향 로직await next()이후: upstream 방향 로직
왜 이 모델이 유용한가
1. 응답 시간 측정
app.use(async (ctx, next) => {
const start = Date.now();
await next(); // 실제 handler 실행
const ms = Date.now() - start; // handler 끝난 후
ctx.set('X-Response-Time', `${ms}ms`);
});
2. 에러 catch
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {error: err.message};
ctx.app.emit('error', err, ctx);
}
});
3. 응답 wrapping
app.use(async (ctx, next) => {
await next();
if (ctx.body && typeof ctx.body === 'object') {
ctx.body = {
success: true,
data: ctx.body,
timestamp: new Date().toISOString(),
};
}
});
4. 트레이싱
app.use(async (ctx, next) => {
const span = tracer.startSpan('http.request');
ctx.state.span = span;
try {
await next();
span.setStatus({code: 0});
} catch (err) {
span.setStatus({code: 2, message: err.message});
throw err;
} finally {
span.end();
}
});
Middleware 종류
함수 (async)
app.use(async (ctx, next) => {
// ...
});
함수 (일반 Promise 리턴)
app.use((ctx, next) => {
return doAsyncWork().then(() => next());
});
Generator function (deprecated, Koa v1 스타일)
Koa v3 는 지원 안 함.
조건부 실행
app.use(async (ctx, next) => {
if (ctx.path.startsWith('/api')) {
await next();
return;
}
ctx.body = 'not api';
});
Middleware 등록 순서
app.use(errorHandler);
app.use(logger);
app.use(cors());
app.use(helmet());
app.use(bodyParser());
app.use(session);
app.use(authMiddleware);
app.use(router.routes());
app.use(router.allowedMethods());
관용:
- 최외곽: error handler (모든 예외 catch)
- 관측: logger, request-id, tracing
- 보안: cors, helmet
- 파싱: body parser, cookies
- 인증: session, auth
- 라우팅: router
Async 안 하는 미들웨어
app.use((ctx, next) => {
ctx.set('X-Powered-By', 'Koa');
return next();
});
ctx.body 만 세팅하고 next() 없이 종료:
app.use(async (ctx) => {
ctx.body = 'Static response';
});
next() 안 부르면?
app.use(async (ctx, next) => {
ctx.body = 'stopped here';
// await next() 없음
});
app.use(async (ctx) => {
ctx.body = 'never reached';
});
두 번째 미들웨어 실행 안 됨. 조건부 short-circuit 에 활용.
koa-compose (여러 미들웨어를 하나로)
const compose = require('koa-compose');
const middlewareStack = compose([
errorHandler,
logger,
bodyParser(),
]);
app.use(middlewareStack);
Router 내부에서도 활용.
Conditional middleware (paths)
Koa 자체는 path 별 미들웨어 지원 X. 조건부로 직접:
app.use(async (ctx, next) => {
if (!ctx.path.startsWith('/admin')) {
return next();
}
// admin 만
if (!ctx.state.user?.isAdmin) {
ctx.throw(403);
}
await next();
});
또는 koa-mount:
const mount = require('koa-mount');
const adminApp = new Koa();
adminApp.use(adminAuthMiddleware);
adminApp.use(adminRouter.routes());
app.use(mount('/admin', adminApp));
Error propagation
Middleware 안에서 throw 하면 upstream 으로 전파. 첫 error handler 가 catch:
app.use(async (ctx, next) => {
try { await next(); } catch (err) { /* handle */ }
});
app.use(async (ctx, next) => {
throw new Error('boom'); // 위 catch 로 감
});
ctx.throw() 도 같은 방식 (http-errors).
App emit error
app.on('error', (err, ctx) => {
logger.error('Unhandled error:', err);
Sentry.captureException(err);
});
Middleware chain 밖에서도 처리 가능 (default handler 실행 후 emit).
공식 미들웨어
@koa/* scope:
@koa/router: 라우팅@koa/cors: CORS@koa/multer: multipart@koa/bodyparser: body parsing
Community:
koa-helmet: 보안 헤더koa-session: sessionkoa-static: 정적 파일koa-mount: sub-app mountingkoa-compress: gzip/brotlikoa-logger: 개발 로그koa-pino-logger: JSON 로그koa-jwt: JWT 인증koa-ratelimit: rate limitingkoa-passport: Passport 통합koa-response-time: X-Response-Time
사용자 정의 미들웨어
옵션 받는 factory 형태 관용:
function requestId(options = {}) {
const headerName = options.headerName || 'X-Request-Id';
return async (ctx, next) => {
const id = ctx.get(headerName) || crypto.randomUUID();
ctx.state.requestId = id;
ctx.set(headerName, id);
await next();
};
}
app.use(requestId({headerName: 'X-Trace-ID'}));
미들웨어 안에서 next 여러 번 호출
app.use(async (ctx, next) => {
await next();
await next(); // Error: next() called multiple times
});
next 는 한 번만.
Middleware 성능
- 각 미들웨어는 async 함수 호출 오버헤드
- 20 개 넘어가면 latency 눈에 띔
- 필수만 등록
함정
WARNING
await next() 잊으면 chain 이탈. 다음 미들웨어 실행 안 되고 응답 empty.
CAUTION
next() 두 번 호출 = 오류. Koa-compose 가 감지.
WARNING
동기 미들웨어에서 next() return 안 하면 후속 흐름 안 됨. return next() 습관.
IMPORTANT
Middleware 순서. Error handler outermost, router innermost.
CAUTION
Middleware 안 blocking I/O 금지. Sync fs, JSON.parse 큰 payload 등. Event loop 정지.
관련 위키
- Koa.js - 상위 개요
- Koa Context - ctx 사용
- Koa Router - Router 미들웨어
- Koa Body Parsing
- Koa Error Handling
- Koa Testing
- JavaScript async/await
- NestJS Middleware - 대비
- NestJS Interceptors - 대비 (Onion 유사)
- FastAPI Middleware
이 글의 용어 (10개)
- [FastAPI] Middlewarefastapi
- 정의 FastAPI Middleware 는 모든 요청과 응답을 가로채 로깅, 인증, 압축, CORS 등 공통 처리를 하는 계층입니다. Starlette 의 ASGI middlew…
- [Framework] Koa.jsframeworks
- 정의 Koa.js 는 Express 창시자 TJ Holowaychuk 이 2013년 발표한 Node.js 웹 프레임워크 입니다. Express 의 후계자 성격이며, 미들웨어를 a…
- [Javascript] async/awaitjavascript
- 정의 / 는 기반 비동기 코드를 마치 동기 코드처럼 쓸 수 있게 해주는 ES2017 의 문법 설탕. 본질은 Promise 그 자체, 문법만 다르다. 전체 동작 메커니즘은 글 참조…
- [Koa] Body Parsingframeworks
- 정의 Koa Body Parsing 은 HTTP 요청 body 를 파싱해 에 사용 가능한 형태로 만드는 미들웨어입니다. Koa 코어는 body parser 를 포함하지 않으므로 …
- [Koa] Context (ctx)frameworks
- 정의 Koa Context ( ) 는 각 요청마다 생성되어 미들웨어와 route handler 에 전달되는 객체입니다. (Node 원본), (Koa 확장), (Koa 확장), (…
- [Koa] Error Handlingframeworks
- 정의 Koa Error Handling 은 미들웨어 chain 에서 발생한 예외를 catch 하여 응답을 정형화하는 패턴입니다. Koa 는 자동 error handler 를 제공…
- [Koa] Router (@koa/router)frameworks
- 정의 @koa/router 는 Koa 의 공식 라우터입니다. Koa 코어는 라우팅을 제공하지 않으므로 별도 설치가 필요합니다. Express 스타일 라우팅 (HTTP method…
- [Koa] Testing (Supertest, Jest, Vitest)frameworks
- 정의 Koa Testing 은 (HTTP 호출) + / (assertions/runner) 조합이 표준입니다. 을 Supertest 에 전달하면 실제 HTTP 서버 없이 요청/응…
- [NestJS] Interceptorsframeworks
- 정의 NestJS Interceptor 는 요청 처리 전/후에 로직을 삽입하는 클래스입니다. AOP (Aspect-Oriented Programming) 패턴의 구현체로, 로깅,…
- [NestJS] Middlewareframeworks
- 정의 NestJS Middleware 는 route handler 실행 이전, Guard 도 실행되기 전에 실행되는 함수/클래스입니다. Express/Fastify 표준 midd…
💬 댓글