Middleware
미들웨어
요청/응답 사이 처리 수행. 인증, 로깅, 에러 처리에 활용.
미들웨어
요청/응답 사이 처리 수행. 인증, 로깅, 에러 처리에 활용.
Middleware(미들웨어)는 웹 애플리케이션에서 요청(Request)과 응답(Response) 사이에서 실행되는 함수입니다. 클라이언트의 요청이 최종 핸들러(라우터, 컨트롤러)에 도달하기 전에 공통 로직을 처리하거나, 응답이 클라이언트에게 전송되기 전에 후처리를 수행합니다. 이를 통해 코드 중복을 줄이고 관심사를 분리할 수 있습니다.
미들웨어의 대표적인 사용 사례로는 인증/인가(Authentication/Authorization), 요청 로깅, CORS 헤더 설정, 요청 본문 파싱, 에러 핸들링 등이 있습니다. Express.js에서는 app.use()로 미들웨어를 등록하고, next() 함수를 호출해 다음 미들웨어로 제어를 전달합니다.
미들웨어는 실행 순서가 중요합니다. 등록된 순서대로 실행되므로, 인증 미들웨어는 보호된 라우트보다 먼저 등록해야 합니다. 또한 에러 핸들링 미들웨어는 다른 모든 미들웨어 후에 등록하여 전역 에러를 처리합니다. 미들웨어 체인(chain) 개념을 이해하면 복잡한 요청 처리 파이프라인을 구성할 수 있습니다.
현대 프레임워크들은 각자의 미들웨어 패턴을 제공합니다. Next.js는 middleware.ts 파일을 통해 Edge Runtime에서 실행되는 미들웨어를 지원하고, Koa는 async/await 기반의 우아한 미들웨어 모델을 제공합니다. 이러한 미들웨어 패턴은 AOP(관점 지향 프로그래밍)의 웹 버전으로, 횡단 관심사(cross-cutting concerns)를 효과적으로 처리합니다.
// Express.js 미들웨어 패턴
import express, { Request, Response, NextFunction } from 'express';
const app = express();
// 1. 로깅 미들웨어 - 모든 요청 기록
const loggerMiddleware = (req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
// 응답 완료 후 로그 기록
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} - ${res.statusCode} (${duration}ms)`);
});
next(); // 다음 미들웨어로 전달
};
// 2. 인증 미들웨어 - JWT 토큰 검증
interface AuthRequest extends Request {
user?: { id: string; email: string; role: string };
}
const authMiddleware = async (req: AuthRequest, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = verifyToken(token); // JWT 검증 함수
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
// 3. 권한 확인 미들웨어 - 역할 기반 접근 제어
const requireRole = (...roles: string[]) => {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
// 4. 에러 핸들링 미들웨어 (4개의 인자)
const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
console.error('Error:', err.stack);
if (err.name === 'ValidationError') {
return res.status(400).json({ error: err.message });
}
if (err.name === 'UnauthorizedError') {
return res.status(401).json({ error: 'Invalid token' });
}
res.status(500).json({ error: 'Internal server error' });
};
// 미들웨어 등록 (순서 중요!)
app.use(express.json()); // 요청 본문 파싱
app.use(loggerMiddleware); // 모든 요청 로깅
// 공개 라우트
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// 인증 필요 라우트
app.get('/api/profile', authMiddleware, (req: AuthRequest, res) => {
res.json({ user: req.user });
});
// 관리자 전용 라우트
app.delete('/api/users/:id',
authMiddleware,
requireRole('admin'),
(req, res) => {
res.json({ message: 'User deleted' });
}
);
// 에러 핸들러는 가장 마지막에 등록
app.use(errorHandler);
// === Next.js Middleware (Edge Runtime) ===
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 인증 확인
const token = request.cookies.get('auth-token');
// 보호된 경로 체크
if (pathname.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
// 응답 헤더 추가
const response = NextResponse.next();
response.headers.set('x-custom-header', 'my-value');
return response;
}
// 미들웨어가 실행될 경로 지정
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*']
};
"API마다 인증 코드가 중복되어 있네요. 유지보수가 어려울 것 같은데 어떻게 개선하면 좋을까요?"
"인증 로직을 미들웨어로 분리하면 됩니다. authMiddleware를 만들어서 보호가 필요한 라우트 앞에 적용하면, 코드 중복 없이 일관된 인증을 적용할 수 있어요."
"에러 로그가 API마다 다르게 남고 있어서 모니터링이 힘들어요."
"전역 에러 핸들링 미들웨어를 만들어서 모든 에러를 한 곳에서 처리하면 됩니다. 미들웨어 체인 가장 마지막에 4개 인자를 받는 에러 핸들러를 등록하면, try-catch 없이도 모든 에러를 잡아서 일관된 형식으로 로깅할 수 있어요."
"미들웨어에서 next()를 호출하지 않으면 어떻게 되나요?"
"요청이 다음 미들웨어나 라우트 핸들러로 전달되지 않고 멈춥니다. 의도적으로 응답을 보내는 경우(인증 실패 등)가 아니라면 반드시 next()를 호출해야 해요. 그렇지 않으면 클라이언트는 응답을 받지 못하고 타임아웃이 발생합니다."
미들웨어 순서: 미들웨어는 등록된 순서대로 실행됩니다. 인증 미들웨어는 보호된 라우트보다 먼저, 에러 핸들러는 가장 마지막에 등록해야 합니다. 순서가 잘못되면 인증이 우회되거나 에러가 처리되지 않습니다.
next() 누락: 미들웨어에서 응답을 보내지 않는 경우 반드시 next()를 호출해야 합니다. 누락하면 요청이 무한 대기 상태에 빠지고, async 미들웨어에서는 에러가 발생해도 next(error)를 호출하거나 에러를 throw해야 에러 핸들러가 작동합니다.
성능 고려: 모든 요청에 실행되는 미들웨어는 성능에 직접적인 영향을 줍니다. 무거운 연산이나 외부 API 호출은 필요한 라우트에만 적용하고, 캐싱을 활용하세요. Next.js 미들웨어는 Edge Runtime에서 실행되므로 Node.js API 전체를 사용할 수 없습니다.