SHIN
Node.js 실전 팁 20선
20편Express의 힘은 미들웨어 체인에 있습니다. 올바른 패턴을 이해하면 확장성 있는 API를 만들 수 있습니다.
// 일반 미들웨어: (req, res, next)
function logger(req, res, next) {
console.log(`${req.method} ${req.path}`);
next(); // 반드시 호출해야 체인이 이어짐
}
// 에러 미들웨어: (err, req, res, next) — 인자 4개
function errorHandler(err, req, res, next) {
res.status(err.status || 500).json({ error: err.message });
}// 설정 가능한 미들웨어 생성
function rateLimit({ max = 100, windowMs = 60_000 } = {}) {
const counts = new Map();
return (req, res, next) => {
const key = req.ip;
const now = Date.now();
const entry = counts.get(key) || { count: 0, resetAt: now + windowMs };
if (now > entry.resetAt) {
entry.count = 0;
entry.resetAt = now + windowMs;
}
entry.count++;
counts.set(key, entry);
if (entry.count > max) {
return res.status(429).json({ error: 'Too Many Requests' });
}
next();
};
}
app.use(rateLimit({ max: 200, windowMs: 60_000 }));// Express는 async 에러를 자동으로 잡지 않음
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// 사용
app.get('/users', asyncHandler(async (req, res) => {
const users = await db.user.findMany();
res.json(users);
}));// 커스텀 에러 클래스
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.statusCode = statusCode;
this.name = 'AppError';
}
}
// 글로벌 핸들러 (반드시 마지막에 등록)
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const isDev = process.env.NODE_ENV === 'development';
console.error(err);
res.status(statusCode).json({
error: {
message: err.message,
...(isDev && { stack: err.stack }),
},
});
});
// 사용
app.get('/post/:id', asyncHandler(async (req, res) => {
const post = await db.post.findById(req.params.id);
if (!post) throw new AppError('포스트를 찾을 수 없습니다', 404);
res.json(post);
}));// routes/users.js
import { Router } from 'express';
const router = Router();
router.get('/', asyncHandler(getUsers));
router.post('/', validate(createUserSchema), asyncHandler(createUser));
router.get('/:id', asyncHandler(getUserById));
export default router;
// app.js
import userRoutes from './routes/users.js';
app.use('/api/users', userRoutes);// 모든 라우트 아래에 위치
app.use((req, res) => {
res.status(404).json({ error: `${req.path} 경로를 찾을 수 없습니다` });
});