|
| 1 | +--- |
| 2 | +title: Middlewares |
| 3 | +description: |
| 4 | +--- |
| 5 | + |
| 6 | +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. |
| 7 | + |
| 8 | +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. |
| 9 | + |
| 10 | +## Creating Middleware |
| 11 | + |
| 12 | +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. |
| 13 | + |
| 14 | +### `onRequest` |
| 15 | + |
| 16 | +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. |
| 17 | + |
| 18 | +:::code-group |
| 19 | +```ts [LoggingMiddleware.ts] |
| 20 | +import { BaseMiddleware } from '@vercube/core'; |
| 21 | +import type { MiddlewareOptions } from '@vercube/core'; |
| 22 | + |
| 23 | +export class LoggingMiddleware extends BaseMiddleware { |
| 24 | + |
| 25 | + public async onRequest( |
| 26 | + request: Request, |
| 27 | + response: Response, |
| 28 | + opts: MiddlewareOptions |
| 29 | + ): Promise<void> { |
| 30 | + console.log(`[${new Date().toISOString()}] ${request.method} ${request.url}`); |
| 31 | + } |
| 32 | + |
| 33 | +} |
| 34 | + |
| 35 | +``` |
| 36 | +```ts [FooController.ts] |
| 37 | +import { Controller, Get, Middleware } from '@vercube/core'; |
| 38 | +import { LoggingMiddleware } from '@/middlewares/LoggingMiddleware'; |
| 39 | + |
| 40 | +@Controller('/users') |
| 41 | +@Middleware(LoggingMiddleware, { logLevel: 'debug' }) |
| 42 | +export class UserController { |
| 43 | + // ... |
| 44 | +} |
| 45 | + |
| 46 | +``` |
| 47 | +::: |
| 48 | + |
| 49 | +::note |
| 50 | +If the `onRequest` method returns a `Response` object (including `FastResponse`) or throws an HTTP error - the endpoint handler will not be invoked. |
| 51 | +:: |
| 52 | + |
| 53 | +### `onResponse` |
| 54 | + |
| 55 | +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. |
| 56 | + |
| 57 | +:::code-group |
| 58 | +```ts [ResponseLoggingMiddleware.ts] |
| 59 | +import { BaseMiddleware } from '@vercube/core'; |
| 60 | +import type { MiddlewareOptions } from '@vercube/core'; |
| 61 | + |
| 62 | +interface IMyData { |
| 63 | + name: string; |
| 64 | + age: number; |
| 65 | +} |
| 66 | + |
| 67 | +export class ResponseLoggingMiddleware extends BaseMiddleware<{}, IMyData> { |
| 68 | + |
| 69 | + public async onResponse( |
| 70 | + request: Request, |
| 71 | + response: Response, |
| 72 | + payload: IMyData, |
| 73 | + ): Promise<void> { |
| 74 | + console.log(`[${new Date().toISOString()}] Response: ${JSON.stringify(payload)}`); |
| 75 | + } |
| 76 | + |
| 77 | +} |
| 78 | +``` |
| 79 | +```ts [FooController.ts] |
| 80 | +import { Controller, Get, Middleware } from '@vercube/core'; |
| 81 | +import { ResponseLoggingMiddleware } from '@/middlewares/ResponseLoggingMiddleware'; |
| 82 | + |
| 83 | +@Controller('/users') |
| 84 | +@Middleware(ResponseLoggingMiddleware, { logLevel: 'debug' }) |
| 85 | +export class UserController { |
| 86 | + // ... |
| 87 | +} |
| 88 | +``` |
| 89 | +::: |
| 90 | + |
| 91 | +::note |
| 92 | +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. |
| 93 | +:: |
| 94 | + |
| 95 | +::warning |
| 96 | +**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. |
| 97 | +:: |
| 98 | + |
| 99 | + |
| 100 | +## Applying Middleware |
| 101 | +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. |
| 102 | + |
| 103 | +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. |
| 104 | + |
| 105 | +```ts [FooController.ts] |
| 106 | +import { Controller, Get, Middleware } from '@vercube/core'; |
| 107 | +import { LoggingMiddleware } from '@/middlewares/LoggingMiddleware'; |
| 108 | + |
| 109 | +@Controller('/users') |
| 110 | +@Middleware(LoggingMiddleware, { logLevel: 'debug' }) |
| 111 | +export class UserController { |
| 112 | + // ... |
| 113 | +} |
| 114 | + |
| 115 | +``` |
| 116 | + |
| 117 | +## Middleware Prioritization |
| 118 | + |
| 119 | +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. |
| 120 | + |
| 121 | +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. |
| 122 | + |
| 123 | +```ts [FooController.ts] |
| 124 | +import { Controller, Get, Middleware } from '@vercube/core'; |
| 125 | +import { LoggingMiddleware } from '@/middlewares/LoggingMiddleware'; |
| 126 | + |
| 127 | +@Controller('/users') |
| 128 | +@Middleware(LoggingMiddleware, { priority: 1 }) |
| 129 | +export class UserController { |
| 130 | + // ... |
| 131 | +} |
| 132 | + |
| 133 | +``` |
| 134 | + |
| 135 | +::note |
| 136 | +The default `priority` value is `999`. |
| 137 | +:: |
| 138 | + |
| 139 | +## Global Middlewares |
| 140 | + |
| 141 | +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`. |
| 142 | + |
| 143 | +You can register global middleware during your application setup: |
| 144 | + |
| 145 | +```ts [setup.ts] |
| 146 | +import { type App } from '@vercube/core'; |
| 147 | +import { LoggingMiddleware } from '@/middlewares/LoggingMiddleware'; |
| 148 | + |
| 149 | +export function setup(app: App): void { |
| 150 | + const registry = app.container.get(GlobalMiddlewareRegistry); |
| 151 | + |
| 152 | + registry.registerGlobalMiddleware(LoggingMiddleware, { priority: 1 }); |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +Once registered, the middleware will be executed for every endpoint in your application. |
0 commit comments