Express.js 라우트 미들웨어 순서 문제
Understanding how middleware execution order affects request processing in Express.js and debugging order-related issues.
Express.js 라우트 미들웨어 순서 문제
Introduction
Express.js processes middleware in the exact order they are defined. A misplaced middleware can cause authentication bypasses, missing request data, and silent failures. This post covers how middleware ordering works and how to debug order-related issues.
Environment
node --version
# v20.11.0
npm list express
# my-app@1.0.0
# +-- express@4.18.2Problem
An authentication middleware was being skipped for certain routes:
const express = require('express');
const app = express();
// Route defined BEFORE middleware
app.get('/admin', (req, res) => {
// This route is accessible without authentication!
res.json({ secret: 'admin data' });
});
// Authentication middleware
app.use((req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
});
// Other routes protected by middleware
app.get('/dashboard', (req, res) => {
res.json({ data: 'protected' });
});The /admin route was accessible without any authentication because it was defined before the auth middleware.
Analysis
Express middleware execution follows a strict top-to-bottom order:
Request → middleware1 → middleware2 → route handler → responseIf a route is defined before the middleware, it will never pass through that middleware. The execution model is:
app.use(auth) → Applied to ALL routes defined AFTER this line
app.get('/a') → Protected by auth
app.get('/b') → Protected by auth
app.use(auth2) → Applied to routes AFTER this line
app.get('/c') → Protected by auth AND auth2The key insight is that app.use() and app.get() are executed in the order they appear in the code, and each middleware decides whether to call next() to continue the chain.
Solution
Solution 1: Define middleware before routes
const express = require('express');
const app = express();
// 1. Body parsing middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 2. Logging middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
});
// 3. Authentication middleware
app.use((req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
});
// 4. Routes (all protected)
app.get('/admin', (req, res) => {
res.json({ secret: 'admin data' });
});
app.get('/dashboard', (req, res) => {
res.json({ data: 'protected' });
});Solution 2: Route-specific middleware
function authMiddleware(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
// Apply middleware only to specific routes
app.get('/admin', authMiddleware, (req, res) => {
res.json({ secret: 'admin data' });
});
app.get('/dashboard', authMiddleware, (req, res) => {
res.json({ data: 'protected' });
});
// Public route - no middleware
app.get('/login', (req, res) => {
res.json({ message: 'Login page' });
});Solution 3: Middleware router pattern
const express = require('express');
const app = express();
// Public routes
const publicRouter = express.Router();
publicRouter.get('/login', (req, res) => res.json({ message: 'Login' }));
publicRouter.post('/register', (req, res) => res.json({ message: 'Register' }));
// Protected routes
const protectedRouter = express.Router();
protectedRouter.use((req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
});
protectedRouter.get('/admin', (req, res) => res.json({ secret: 'data' }));
protectedRouter.get('/dashboard', (req, res) => res.json({ data: 'protected' }));
app.use('/api', publicRouter);
app.use('/api', protectedRouter);Solution 4: Debug middleware order
// Debug tool to trace middleware execution
function debugMiddleware(name) {
return (req, res, next) => {
console.log(`[${name}] Executing for ${req.method} ${req.url}`);
next();
};
}
// Add to each middleware
app.use(debugMiddleware('json-parser'), express.json());
app.use(debugMiddleware('auth'), authMiddleware);
app.use(debugMiddleware('logger'), loggerMiddleware);
app.get('/test', debugMiddleware('route-handler'), (req, res) => {
res.json({ message: 'Hello' });
});Solution 5: Conditional middleware
// Skip middleware for certain routes
function conditionalMiddleware(condition, middleware) {
return (req, res, next) => {
if (condition(req)) {
return next();
}
middleware(req, res, next);
};
}
// Skip auth for public paths
const isPublicPath = (req) =>
req.path.startsWith('/login') ||
req.path.startsWith('/register') ||
req.path.startsWith('/health');
app.use(conditionalMiddleware(isPublicPath, authMiddleware));Lessons Learned
- Always define middleware before routes - This is the most common ordering mistake
- Use route-specific middleware when different routes need different middleware stacks
- Create router modules for complex applications with many middleware layers
- Add debug logging to trace middleware execution order
- Test middleware order with integration tests that verify authentication and authorization
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.