Files
APAW/.kilo/skills/nodejs-middleware-patterns/SKILL.md
¨NW¨ 8fcd8f8a9b feat: add comprehensive NodeJS development skills and rules
Based on Planner and Memory Manager analysis:

New Skills (8):
- nodejs-express-patterns: App structure, routing, middleware
- nodejs-security-owasp: OWASP Top 10 security practices
- nodejs-testing-jest: Unit/integration tests, mocking
- nodejs-auth-jwt: JWT authentication, OAuth, sessions
- nodejs-error-handling: Error classes, middleware, async handlers
- nodejs-middleware-patterns: Auth, validation, rate limiting
- nodejs-db-patterns: SQLite, PostgreSQL, MongoDB patterns
- nodejs-npm-management: package.json, scripts, dependencies

New Rules:
- nodejs.md: Code style, security, best practices

Updated:
- backend-developer.md: Added skills reference table

Milestone: #48 NodeJS Development Coverage
Related: Planner & Memory Manager analysis results
2026-04-05 02:39:06 +01:00

7.7 KiB

NodeJS Middleware Patterns

Comprehensive middleware patterns for Express.js applications.

Middleware Types

1. Application-level Middleware

// Global middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(helmet());
app.use(cors());

// Conditional middleware
app.use((req, res, next) => {
  if (req.path.startsWith('/api')) {
    return apiLimiter(req, res, next);
  }
  next();
});

2. Router-level Middleware

const router = require('express').Router();

// Router-specific middleware
router.use(authenticate);
router.use(validateRequest);

// Route-specific middleware
router.post('/', validateUser, controller.create);
router.put('/:id', validateUser, authenticate, controller.update);

3. Error-handling Middleware

// Must have 4 parameters
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.statusCode || 500).json({
    status: 'error',
    message: err.message
  });
});

4. Built-in Middleware

app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

Common Patterns

Authentication Middleware

function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ message: 'Unauthorized' });
  }
  
  const token = authHeader.split(' ')[1];
  
  try {
    const decoded = verifyToken(token);
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({ message: 'Invalid token' });
  }
}

Authorization Middleware

function authorize(...roles) {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ message: 'Forbidden' });
    }
    next();
  };
}

// Usage
router.delete('/:id', authenticate, authorize('admin'), controller.delete);

Rate Limiting Middleware

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
  message: { message: 'Too many requests' }
});

app.use('/api', limiter);

Validation Middleware

const { validationResult, body } = require('express-validator');

function validate(validations) {
  return async (req, res, next) => {
    await Promise.all(validations.map(v => v.run(req)));
    
    const errors = validationResult(req);
    if (errors.isEmpty()) return next();
    
    res.status(400).json({
      status: 'fail',
      errors: errors.array().map(err => ({
        field: err.path,
        message: err.msg
      }))
    });
  };
}

// Usage
router.post('/users',
  validate([
    body('email').isEmail(),
    body('password').isLength({ min: 8 })
  ]),
  controller.create
);

Request Logging Middleware

const pino = require('pino');
const logger = pino();

function requestLogger(req, res, next) {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    logger.info({
      method: req.method,
      path: req.path,
      status: res.statusCode,
      duration,
      user: req.user?.id
    });
  });
  
  next();
}

Context Middleware

// Request-scoped context
const asyncLocalStorage = new (require('async_hooks').AsyncLocalStorage)();

function contextMiddleware(req, res, next) {
  const context = {
    requestId: require('uuid').v4(),
    userId: req.user?.id,
    startTime: Date.now()
  };
  
  asyncLocalStorage.run(context, next);
}

// Access context anywhere in the request lifecycle
function getContext() {
  return asyncLocalStorage.getStore();
}

Error Handler Middleware

function errorHandler(err, req, res, next) {
  const statusCode = err.statusCode || 500;
  const status = err.status || 'error';
  
  if (process.env.NODE_ENV === 'development') {
    return res.status(statusCode).json({
      status,
      message: err.message,
      stack: err.stack
    });
  }
  
  if (err.isOperational) {
    return res.status(statusCode).json({
      status,
      message: err.message
    });
  }
  
  res.status(500).json({
    status: 'error',
    message: 'Something went wrong'
  });
}

Third-party Middleware

Security (Helmet)

const helmet = require('helmet');

app.use(helmet());
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"]
  }
}));

Compression

const compression = require('compression');

app.use(compression({
  threshold: 1024, // Only compress >1KB
  filter: (req, res) => {
    if (req.headers['x-no-compression']) return false;
    return compression.filter(req, res);
  }
}));

CORS

const cors = require('cors');

app.use(cors({
  origin: process.env.CORS_ORIGIN?.split(',') || '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

Morgan (Logging)

const morgan = require('morgan');

app.use(morgan('dev')); // Development
app.use(morgan('combined')); // Production

Custom Middleware Factory

// Custom middleware factory
function createMiddleware(options) {
  return (req, res, next) => {
    // Pre-processing
    if (options.preCondition && !options.preCondition(req)) {
      return res.status(400).json({ message: 'Precondition failed' });
    }
    
    // Modify request
    req.custom = options.transform?.(req) || req.custom;
    
    // Post-processing (on response)
    res.on('finish', () => {
      options.postProcess?.(req, res);
    });
    
    next();
  };
}

// Usage
app.use(createMiddleware({
  preCondition: (req) => req.headers['x-api-key'],
  transform: (req) => ({ ip: req.ip }),
  postProcess: (req, res) => console.log(`Request completed: ${req.path}`)
}));

Async Middleware Wrapper

function asyncHandler(fn) {
  return (req, res, next) => 
    Promise.resolve(fn(req, res, next)).catch(next);
}

// Wrap middleware
app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  res.json({ user });
}));

Middleware Composition

// Compose multiple middleware
function compose(...middlewares) {
  return (req, res, next) => {
    let index = -1;
    
    function dispatch(i) {
      if (i <= index) return next(new Error('next() called multiple times'));
      index = i;
      
      const middleware = middlewares[i];
      if (!middleware) return next();
      
      return middleware(req, res, (err) => {
        if (err) return next(err);
        return dispatch(i + 1);
      });
    }
    
    return dispatch(0);
  };
}

// Usage
app.use(compose(
  authenticate,
  authorize('user'),
  validateRequest
));

Best Practices

  1. Order matters - Put error handlers last
  2. Use router middleware - For route groups
  3. Use async handlers - Wrap async functions
  4. Keep it simple - One responsibility per middleware
  5. Use third-party - Don't reinvent the wheel
  6. Document dependencies - Comment required fields

Middleware Order

// Correct order
app.use(helmet());              // Security headers
app.use(cors());                // CORS
app.use(compression());         // Compression
app.use(express.json());        // Body parsing
app.use(express.urlencoded()); // URL encoding
app.use(morgan('dev'));         // Logging
app.use('/api', apiLimiter);    // Rate limiting
app.use('/api', routes);        // Routes
app.use(notFoundHandler);       // 404 handler
app.use(errorHandler);          // Error handler