Dwex Logo

Middleware

Process requests and responses with middleware functions

What is Middleware?

Middleware functions execute before route handlers, allowing you to process requests, modify responses, or perform cross-cutting concerns like logging, authentication, and CORS.

Built-in Middleware

Dwex provides several built-in middleware functions:

CORS Middleware

Enable Cross-Origin Resource Sharing:

import { DwexFactory } from "@dwex/core";
import { corsMiddleware } from "@dwex/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await DwexFactory.create(AppModule);

  // Enable CORS
  app.use(
    corsMiddleware({
      origin: "https://example.com",
      credentials: true,
    })
  );

  await app.listen(3000);
}

bootstrap();

CORS Options:

interface CorsOptions {
  origin?: string | string[] | ((origin: string) => boolean);
  methods?: string | string[];
  allowedHeaders?: string | string[];
  exposedHeaders?: string | string[];
  credentials?: boolean;
  maxAge?: number;
  preflightContinue?: boolean;
  optionsSuccessStatus?: number;
}

Examples:

// Allow all origins
app.use(corsMiddleware({ origin: "*" }));

// Allow specific origins
app.use(
  corsMiddleware({
    origin: ["https://app.com", "https://admin.app.com"],
  })
);

// Dynamic origin validation
app.use(
  corsMiddleware({
    origin: (origin) => origin.endsWith(".myapp.com"),
  })
);

// Full configuration
app.use(
  corsMiddleware({
    origin: "https://app.com",
    methods: ["GET", "POST", "PUT", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
    credentials: true,
    maxAge: 86400,
  })
);

Body Parser

Parse JSON request bodies (enabled by default):

// Automatically available in all controllers
@Post()
create(@Body() data: CreateUserDto) {
	return data; // Already parsed as JSON
}

Parse cookies from requests (enabled by default):

@Get()
getCookies(@Cookies() cookies: Record<string, string>) {
	return cookies; // Automatically parsed
}

@Get('session')
getSession(@Cookies('sessionId') sessionId: string) {
	return { sessionId };
}

Custom Middleware

Create custom middleware functions:

Function-Based Middleware

import type { MiddlewareFunction } from "@dwex/core";

const loggingMiddleware: MiddlewareFunction = (req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
};

// Apply globally
app.use(loggingMiddleware);

Class-Based Middleware

import { Injectable, type DwexMiddleware } from "@dwex/core";

@Injectable()
export class LoggerMiddleware implements DwexMiddleware {
  use(req: any, res: any, next: () => void) {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
    next();
  }
}

Register in module:

import { Module } from "@dwex/core";
import { LoggerMiddleware } from "./logger.middleware";

@Module({
  providers: [LoggerMiddleware],
})
export class AppModule {}

Apply to controllers:

import { Controller, Get, UseMiddleware } from "@dwex/core";
import { LoggerMiddleware } from "./logger.middleware";

@Controller("users")
@UseMiddleware(LoggerMiddleware)
export class UserController {
  @Get()
  findAll() {
    return [];
  }
}

Global Middleware

Apply middleware to all routes:

async function bootstrap() {
  const app = await DwexFactory.create(AppModule);

  // CORS
  app.use(corsMiddleware({ origin: "*" }));

  // Custom logging
  app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();
  });

  await app.listen(3000);
}

Controller-Specific Middleware

Apply middleware to specific controllers:

import { Controller, Get, UseMiddleware } from "@dwex/core";
import { AuthMiddleware } from "./auth.middleware";
import { LoggerMiddleware } from "./logger.middleware";

@Controller("admin")
@UseMiddleware(AuthMiddleware, LoggerMiddleware)
export class AdminController {
  @Get()
  dashboard() {
    return { message: "Admin Dashboard" };
  }
}

Middleware Examples

Authentication Middleware

import { Injectable, UnauthorizedException } from "@dwex/core";
import type { DwexMiddleware } from "@dwex/core";

@Injectable()
export class AuthMiddleware implements DwexMiddleware {
  use(req: any, res: any, next: () => void) {
    const token = req.headers.authorization;

    if (!token) {
      throw new UnauthorizedException("No token provided");
    }

    try {
      const decoded = verifyToken(token);
      req.user = decoded;
      next();
    } catch (error) {
      throw new UnauthorizedException("Invalid token");
    }
  }
}

Rate Limiting

const rateLimiter: MiddlewareFunction = (() => {
  const requests = new Map<string, number[]>();

  return (req, res, next) => {
    const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
    const now = Date.now();
    const windowMs = 60000; // 1 minute
    const maxRequests = 100;

    if (!requests.has(ip)) {
      requests.set(ip, []);
    }

    const ipRequests = requests.get(ip)!;
    const recentRequests = ipRequests.filter((time) => now - time < windowMs);

    if (recentRequests.length >= maxRequests) {
      res.statusCode = 429;
      res.end("Too Many Requests");
      return;
    }

    recentRequests.push(now);
    requests.set(ip, recentRequests);
    next();
  };
})();

app.use(rateLimiter);

Request ID

import { v4 as uuid } from "uuid";

const requestIdMiddleware: MiddlewareFunction = (req, res, next) => {
  req.id = uuid();
  res.setHeader("X-Request-Id", req.id);
  next();
};

Timing Middleware

const timingMiddleware: MiddlewareFunction = (req, res, next) => {
  const start = Date.now();

  // Hook into response finish
  res.on("finish", () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.url} - ${duration}ms`);
  });

  next();
};

Security Headers

const securityHeadersMiddleware: MiddlewareFunction = (req, res, next) => {
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader("X-Frame-Options", "DENY");
  res.setHeader("X-XSS-Protection", "1; mode=block");
  res.setHeader(
    "Strict-Transport-Security",
    "max-age=31536000; includeSubDomains"
  );
  next();
};

Middleware Order

Middleware executes in the order it's registered:

app.use(corsMiddleware());
app.use(loggingMiddleware); // Runs after CORS
app.use(authMiddleware); // Runs after logging

Controller middleware runs after global middleware:

// Execution order:
// 1. Global middleware (app.use)
// 2. Controller middleware (@UseMiddleware)
// 3. Route handler

Error Handling in Middleware

Throw exceptions from middleware:

const validateApiKey: MiddlewareFunction = (req, res, next) => {
  const apiKey = req.headers["x-api-key"];

  if (!apiKey) {
    throw new UnauthorizedException("API key required");
  }

  if (apiKey !== process.env.API_KEY) {
    throw new ForbiddenException("Invalid API key");
  }

  next();
};

Best Practices

1. Always Call next()

Unless you're ending the response, always call next():

// Good
const middleware = (req, res, next) => {
  console.log("Processing...");
  next();
};

// Bad (blocks all requests)
const middleware = (req, res, next) => {
  console.log("Processing...");
  // Forgot next()!
};

2. Use Class Middleware for DI

When you need dependencies, use class-based middleware:

@Injectable()
export class AuthMiddleware implements DwexMiddleware {
  constructor(
    private readonly jwtService: JwtService,
    private readonly logger: Logger
  ) {}

  use(req: any, res: any, next: () => void) {
    this.logger.log("Validating token");
    const valid = this.jwtService.verify(req.headers.authorization);
    if (valid) next();
  }
}

3. Keep Middleware Focused

Each middleware should have one responsibility:

// Good - separate concerns
app.use(corsMiddleware());
app.use(loggingMiddleware);
app.use(authMiddleware);

// Avoid - too much in one middleware
app.use(corsAndLoggingAndAuthMiddleware);

4. Document Side Effects

Clearly document what middleware modifies:

/**
 * Attaches user object to request after validating JWT token.
 * Adds `req.user` property.
 */
@Injectable()
export class AuthMiddleware implements DwexMiddleware {
  use(req: any, res: any, next: () => void) {
    req.user = this.validateAndDecodeToken(req);
    next();
  }
}

Next Steps