Middlewares

Middlewares in Vercube provides a powerful way to handle and modify requests and responses at different levels of your application. They allow you to execute code before or after specific routes, add common functionality across multiple endpoints, or implement cross-cutting concerns like authentication, logging, or error handling.

The middleware system in Vercube is designed to be flexible and intuitive, while maintaining the framework's high-performance characteristics. Each middleware can access and modify the request and response objects, making it possible to implement various functionalities like request validation, response transformation, or custom header management.

Creating Middleware

In Vercube, to create a middleware, you need to create a class that extends BaseMiddleware. This class provides two methods: onRequest and onResponse, which are invoked at different stages of the request lifecycle.

onRequest

The onRequest method is executed before the endpoint handler is called. It allows you to modify the request, validate input data, check user permissions, or interrupt further request processing by returning a Response object. This is the ideal place to implement authorization logic, request logging, or preliminary data validation.

import { BaseMiddleware } from '@vercube/core';
import type { MiddlewareOptions } from '@vercube/core';

export class LoggingMiddleware extends BaseMiddleware {

  public async onRequest(
    request: Request,
    response: Response,
    opts: MiddlewareOptions
  ): Promise<void> {
    console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`);
  }

}
If the onRequest method returns a Response object (including FastResponse) or throws an HTTP error - the endpoint handler will not be invoked.

onResponse

The onResponse method is executed after the endpoint handler has been called and returns a response. It allows you to modify the response payload, add custom headers, perform post-processing operations, or implement response logging. This is the ideal place to implement response transformation, final logging, or cleanup operations.

import { BaseMiddleware } from '@vercube/core';
import type { MiddlewareOptions } from '@vercube/core';

interface IMyData {
  name: string;
  age: number;
}

export class ResponseLoggingMiddleware extends BaseMiddleware<{}, IMyData> {

  public async onResponse(
    request: Request,
    response: Response,
    payload: IMyData,
  ): Promise<void> {
    console.log(`[${new Date().toISOString()}] Response: ${JSON.stringify(payload)}`);
  }

}
The onResponse method receives the payload parameter, which is the object returned by the endpoint handler. You can modify this payload or perform operations based on its content.
Important: When an endpoint handler returns a Response object (including FastResponse), the middleware will be executed but the response object, including its headers, status code, and body, cannot be modified or overridden within the onResponse method.

Applying Middleware

To apply middleware to your endpoint, use the @Middleware decorator. This decorator can be applied to an entire controller class or to individual methods, giving you fine-grained control over where middleware logic is executed.

When applied at the class level, the middleware will be executed for all endpoints within that controller. When applied at the method level, it will only affect that specific endpoint. You can also combine both approaches - class-level middleware will execute first, followed by method-level middleware, allowing you to create layered middleware chains that handle both general and specific concerns.

FooController.ts
import { Controller, Get, Middleware } from '@vercube/core';
import { LoggingMiddleware } from '@/middlewares/LoggingMiddleware';

@Controller('/users')
@Middleware(LoggingMiddleware, { logLevel: 'debug' })
export class UserController {
  // ...
}

Middleware Prioritization

Vercube provides middleware prioritization capabilities, offering flexible control over execution order and timing. This allows you to precisely manage when and in what sequence middleware components are executed.

To set middleware priority, pass a second argument to the @Middleware decorator containing a priority property. Lower priority values execute earlier in the middleware chain.

FooController.ts
import { Controller, Get, Middleware } from '@vercube/core';
import { LoggingMiddleware } from '@/middlewares/LoggingMiddleware';

@Controller('/users')
@Middleware(LoggingMiddleware, { priority: 1 })
export class UserController {
  // ...
}
The default priority value is 999.

Global Middlewares

In addition to middleware that can be applied to specific endpoints or endpoint groups, Vercube provides the capability to create global middleware. Global middleware functions identically to regular middleware, with the key difference being in their registration process. For global middleware, you must utilize the IOC service GlobalMiddlewareRegistry.

You can register global middleware during your application setup:

setup.ts
import { type App } from '@vercube/core';
import { LoggingMiddleware } from '@/middlewares/LoggingMiddleware';

export function setup(app: App): void {
  const registry = app.container.get(GlobalMiddlewareRegistry);

  registry.registerGlobalMiddleware(LoggingMiddleware, { priority: 1 });
}

Once registered, the middleware will be executed for every endpoint in your application.

Previous

Validation

Automatic request validation with Standard Schema support

Next