# ADR-043: Express Middleware Pipeline Architecture

**Date**: 2026-01-09

**Status**: Accepted

**Implemented**: 2026-01-09

## Context

The Express application uses a layered middleware pipeline to handle cross-cutting concerns:

1. **Security**: Helmet headers, CORS, rate limiting.
2. **Parsing**: JSON body, URL-encoded, cookies.
3. **Authentication**: Session management, JWT verification.
4. **Validation**: Request body/params validation.
5. **File Handling**: Multipart form data, file uploads.
6. **Error Handling**: Centralized error responses.

Middleware ordering is critical - incorrect ordering can cause security vulnerabilities or broken functionality. This ADR documents the canonical middleware order and patterns.

## Decision

We will establish a strict middleware ordering convention:

1. **Security First**: Security headers and protections apply to all requests.
2. **Parsing Before Logic**: Body/cookie parsing before route handlers.
3. **Auth Before Routes**: Authentication middleware before protected routes.
4. **Validation At Route Level**: Per-route validation middleware.
5. **Error Handler Last**: Centralized error handling catches all errors.

### Design Principles

- **Defense in Depth**: Multiple security layers.
- **Fail-Fast**: Reject bad requests early in the pipeline.
- **Explicit Ordering**: Document and enforce middleware order.
- **Route-Level Flexibility**: Specific middleware per route as needed.

## Implementation Details

### Global Middleware Order

Located in `src/server.ts`:

```typescript
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import { requestTimeoutMiddleware } from './middleware/timeout.middleware';
import { rateLimiter } from './middleware/rateLimit.middleware';
import { errorHandler } from './middleware/errorHandler.middleware';

const app = express();

// ============================================
// LAYER 1: Security Headers & Protections
// ============================================
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'unsafe-inline'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        imgSrc: ["'self'", 'data:', 'blob:'],
      },
    },
  }),
);
app.use(
  cors({
    origin: process.env.FRONTEND_URL,
    credentials: true,
  }),
);

// ============================================
// LAYER 2: Request Limits & Timeouts
// ============================================
app.use(requestTimeoutMiddleware(30000)); // 30s default
app.use(rateLimiter); // Rate limiting per IP

// ============================================
// LAYER 3: Body & Cookie Parsing
// ============================================
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(cookieParser());

// ============================================
// LAYER 4: Static Assets (before auth)
// ============================================
app.use('/flyer-images', express.static('flyer-images'));

// ============================================
// LAYER 5: Authentication Setup
// ============================================
app.use(passport.initialize());
app.use(passport.session());

// ============================================
// LAYER 6: Routes (with per-route middleware)
// ============================================
app.use('/api/auth', authRoutes);
app.use('/api/flyers', flyerRoutes);
app.use('/api/admin', adminRoutes);
// ... more routes

// ============================================
// LAYER 7: Error Handling (must be last)
// ============================================
app.use(errorHandler);
```

### Validation Middleware

Located in `src/middleware/validation.middleware.ts`:

```typescript
import { z } from 'zod';
import { Request, Response, NextFunction } from 'express';
import { ValidationError } from '../services/db/errors.db';

export const validate = <T extends z.ZodType>(schema: T) => {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse({
      body: req.body,
      query: req.query,
      params: req.params,
    });

    if (!result.success) {
      const errors = result.error.errors.map((err) => ({
        path: err.path.join('.'),
        message: err.message,
      }));
      return next(new ValidationError(errors));
    }

    // Attach validated data to request
    req.validated = result.data;
    next();
  };
};

// Usage in routes:
router.post('/flyers', authenticate, validate(CreateFlyerSchema), flyerController.create);
```

### File Upload Middleware

Located in `src/middleware/fileUpload.middleware.ts`:

```typescript
import multer from 'multer';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'flyer-images/');
  },
  filename: (req, file, cb) => {
    const ext = path.extname(file.originalname);
    cb(null, `${uuidv4()}${ext}`);
  },
});

const fileFilter = (req: Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('Invalid file type'));
  }
};

export const uploadFlyer = multer({
  storage,
  fileFilter,
  limits: {
    fileSize: 10 * 1024 * 1024, // 10MB
    files: 10, // Max 10 files per request
  },
});

// Usage:
router.post('/flyers/upload', uploadFlyer.array('files', 10), flyerController.upload);
```

### Authentication Middleware

Located in `src/middleware/auth.middleware.ts`:

```typescript
import passport from 'passport';
import { Request, Response, NextFunction } from 'express';

// Require authenticated user
export const authenticate = (req: Request, res: Response, next: NextFunction) => {
  passport.authenticate('jwt', { session: false }, (err, user) => {
    if (err) return next(err);
    if (!user) {
      return res.status(401).json({ error: 'Unauthorized' });
    }
    req.user = user;
    next();
  })(req, res, next);
};

// Require admin role
export const requireAdmin = (req: Request, res: Response, next: NextFunction) => {
  if (!req.user?.role || req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
};

// Optional auth (attach user if present, continue if not)
export const optionalAuth = (req: Request, res: Response, next: NextFunction) => {
  passport.authenticate('jwt', { session: false }, (err, user) => {
    if (user) req.user = user;
    next();
  })(req, res, next);
};
```

### Error Handler Middleware

Located in `src/middleware/errorHandler.middleware.ts`:

```typescript
import { Request, Response, NextFunction } from 'express';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '../services/logger.server';
import { ValidationError, NotFoundError, UniqueConstraintError } from '../services/db/errors.db';

export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
  const errorId = uuidv4();

  // Log error with context
  logger.error(
    {
      errorId,
      err,
      path: req.path,
      method: req.method,
      userId: req.user?.user_id,
    },
    'Request error',
  );

  // Map error types to HTTP responses
  if (err instanceof ValidationError) {
    return res.status(400).json({
      success: false,
      error: { code: 'VALIDATION_ERROR', message: err.message, details: err.errors },
      meta: { errorId },
    });
  }

  if (err instanceof NotFoundError) {
    return res.status(404).json({
      success: false,
      error: { code: 'NOT_FOUND', message: err.message },
      meta: { errorId },
    });
  }

  if (err instanceof UniqueConstraintError) {
    return res.status(409).json({
      success: false,
      error: { code: 'CONFLICT', message: err.message },
      meta: { errorId },
    });
  }

  // Default: Internal Server Error
  return res.status(500).json({
    success: false,
    error: {
      code: 'INTERNAL_ERROR',
      message: process.env.NODE_ENV === 'production' ? 'An unexpected error occurred' : err.message,
    },
    meta: { errorId },
  });
};
```

### Request Timeout Middleware

```typescript
export const requestTimeoutMiddleware = (timeout: number) => {
  return (req: Request, res: Response, next: NextFunction) => {
    res.setTimeout(timeout, () => {
      if (!res.headersSent) {
        res.status(503).json({
          success: false,
          error: { code: 'TIMEOUT', message: 'Request timed out' },
        });
      }
    });
    next();
  };
};
```

## Route-Level Middleware Patterns

### Protected Route with Validation

```typescript
router.put(
  '/flyers/:flyerId',
  authenticate, // 1. Auth check
  validate(UpdateFlyerSchema), // 2. Input validation
  flyerController.update, // 3. Handler
);
```

### Admin-Only Route

```typescript
router.delete(
  '/admin/users/:userId',
  authenticate, // 1. Auth check
  requireAdmin, // 2. Role check
  validate(DeleteUserSchema), // 3. Input validation
  adminController.deleteUser, // 4. Handler
);
```

### File Upload Route

```typescript
router.post(
  '/flyers/upload',
  authenticate, // 1. Auth check
  uploadFlyer.array('files', 10), // 2. File handling
  validate(UploadFlyerSchema), // 3. Metadata validation
  flyerController.upload, // 4. Handler
);
```

### Public Route with Optional Auth

```typescript
router.get(
  '/flyers/:flyerId',
  optionalAuth, // 1. Attach user if present
  flyerController.getById, // 2. Handler (can check req.user)
);
```

## Consequences

### Positive

- **Security**: Defense-in-depth with multiple security layers.
- **Consistency**: Predictable request processing order.
- **Maintainability**: Clear separation of concerns.
- **Debuggability**: Errors caught and logged centrally.
- **Flexibility**: Per-route middleware composition.

### Negative

- **Order Sensitivity**: Middleware order bugs can be subtle.
- **Performance**: Many middleware layers add latency.
- **Complexity**: New developers must understand the pipeline.

### Mitigation

- Document middleware order in comments (as shown above).
- Use integration tests that verify middleware chain behavior.
- Profile middleware performance in production.

## Key Files

- `src/server.ts` - Global middleware registration
- `src/middleware/validation.middleware.ts` - Zod validation
- `src/middleware/fileUpload.middleware.ts` - Multer configuration
- `src/middleware/multer.middleware.ts` - File upload handling
- `src/middleware/errorHandler.middleware.ts` - Error handling (implicit)

## Related ADRs

- [ADR-001](./0001-standardized-error-handling.md) - Error Handling
- [ADR-003](./0003-standardized-input-validation-using-middleware.md) - Input Validation
- [ADR-016](./0016-api-security-hardening.md) - API Security
- [ADR-032](./0032-rate-limiting-strategy.md) - Rate Limiting
- [ADR-033](./0033-file-upload-and-storage-strategy.md) - File Uploads
