Skip to content

Middlewares ​

Middlewares process requests in a pipeline before they reach your controllers. They handle cross-cutting concerns like authentication, logging, validation, and request transformation.

Overview ​

YasuiJS supports two types of middlewares, both built on Web Standards and compatible with all runtimes (Node.js, Deno, Bun):

  1. Class-based middlewares - Use the @Middleware() decorator with dependency injection support
  2. Functional middlewares - Simple functions following the Web Standards Request → Response pattern

Important: YasuiJS 4.x uses Web Standards Request/Response instead of Express. Express-style middlewares (like cors, helmet, etc.) are not compatible. Use Web Standards-compatible alternatives or write native YasuiJS middlewares.

Middlewares can be applied at three levels with different execution priorities:

  1. Application level - Applied to all requests
  2. Controller level - Applied to all routes in a controller
  3. Endpoint level - Applied to specific routes
typescript
import { Middleware } from 'yasui';

@Middleware()
export class LoggingMiddleware {
  use() {
    console.log('Request received');
  }
}

Functional Middlewares ​

Functional middlewares are simple functions that follow the Web Standards Request → Response pattern. They're perfect for third-party integrations, stateless operations, or when you don't need dependency injection.

typescript
import type { YasuiRequest, RequestHandler, NextFunction } from 'yasui';

export function simpleLogger(): RequestHandler {
  return async (req: YasuiRequest, next?: NextFunction): Promise<Response> => {
    console.log(`${req.method} ${req.path}`);
    return next ? next() : new Response(null, { status: 500 });
  };
}

// Usage
yasui.createServer({
  middlewares: [simpleLogger()],
  controllers: [UserController]
});

Third-party compatibility: Functional middlewares work with any library providing Web Standards handlers, such as authentication libraries (e.g., BetterAuth's auth.handler()), official plugins, or custom fetch-compatible handlers.

When to use:

  • Third-party integrations (BetterAuth, etc.)
  • Stateless operations (logging, CORS, rate limiting)
  • No dependency injection needed

When to use classes:

  • Need dependency injection (@Inject())
  • Access to services/database
  • Complex business logic with shared state

Class-based Middlewares ​

Middleware Decorator ​

The @Middleware() decorator marks a class as middleware. The class must implement a use() method. You can optionally implement the IMiddleware interface provided by YasuiJS to enforce the method signature.

typescript
import { Middleware, IMiddleware, Request, Req } from 'yasui';

@Middleware()
export class AuthMiddleware implements IMiddleware {
  use(@Req() req: Request) {
    const token = req.headers.get('authorization');

    if (!token) {
      throw new HttpError(401, 'Unauthorized');
    }
    // Validate token logic here

    // Will continue to next middleware or controller if you return nothing/void
  }
}

Note: Middlewares work like controller methods - you can return values, throw errors, or return nothing to continue. Using @Next() is optional if you need manual control over the execution flow.

Parameter Decorators in Middlewares ​

Middlewares can use the same parameter decorators as controllers and benefit from automatic error catching too:

typescript
@Middleware()
export class ValidationMiddleware {
  use(
    @Body() body: any,
    @Query('validate') shouldValidate: boolean,
    @Header('content-type') contentType: string
  ) {
    if (shouldValidate && !this.isValid(body)) {
      throw new HttpError(400, 'Invalid request data');
    }
  }

  private isValid(data: any): boolean {
    // Validation logic
    return true;
  }
}

Automatic Type Casting: All parameter decorators in middlewares benefit from the same automatic type casting as controllers. Parameters are cast to their specified types before middleware execution.

Dependency Injection ​

As Middleware classes act like Controllers, they also allow dependency injection in the same way:

typescript
@Middleware()
export class LoggingMiddleware {
  constructor (
    private validationService: ValidationService, // Standard injection
    @Inject('CONFIG') private config: AppConfig, // Pre-registered custom injection
  ) {}

  use(
    @Body() body: any,
    @Inject() anotherService: AnotherService, // Same at method level
  ) {
    if (!this.validationService.isValid(body)) {
      throw new HttpError(400, 'Invalid request data');
    }
  }
}

Writing Custom Middlewares ​

You can create middlewares for common use cases. Here are two patterns:

Pattern 1: Simple Validation (No @Next() needed) ​

typescript
@Middleware()
export class ApiKeyMiddleware implements IMiddleware {
  use(@Header('x-api-key') apiKey: string) {
    if (!apiKey || apiKey !== 'expected-key') {
      throw new HttpError(401, 'Invalid API key');
    }
    // Will continue automatically
  }
}

Pattern 2: Response Modification (Using @Next()) ​

When you need to modify the response, use @Next():

typescript
@Middleware()
export class TimingMiddleware implements IMiddleware {
  async use(@Req() req: Request, @Next() next: NextFunction) {
    const start = Date.now();
    const response = await next();
    const duration = Date.now() - start;

    const headers = new Headers(response.headers);
    headers.set('X-Response-Time', `${duration}ms`);

    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers
    });
  }
}

For production-ready CORS handling, use the official @yasui/cors plugin which provides origin validation, preflight handling, credentials support, and modern security features.

Middleware Usage Levels ​

Application Level ​

Applied to all requests across your entire application:

typescript
yasui.createServer({
  controllers: [UserController],
  middlewares: [LoggingMiddleware, SecurityMiddleware]
});

Controller Level ​

Applied to all routes within a specific controller:

typescript
// Single middleware
@Controller('/api/users', AuthMiddleware)
export class UserController {
  // All routes require authentication
}

// Multiple middlewares
@Controller('/api/admin', AuthMiddleware, ValidationMiddleware)
export class AdminController {
  // All routes have auth + validation
}

Endpoint Level ​

Applied to specific routes only:

typescript
@Controller('/api/users')
export class UserController {
  @Get('/')
  getUsers() {
    // No middleware
  }
  
  @Post('/', ValidationMiddleware)
  createUser() {
    // Only validation middleware
  }
  
  @Delete('/:id', AuthMiddleware, ValidationMiddleware)
  deleteUser() {
    // Both auth and validation middlewares
  }
}

Execution Order ​

Middlewares execute in this order:

  1. Application middlewares (in registration order)
  2. Controller middlewares (in declaration order)
  3. Endpoint middlewares (in declaration order)
  4. Controller method
typescript
// Execution order example:
yasui.createServer({
  middlewares: [GlobalMiddleware] // 1. First
});

@Controller('/users', ControllerMiddleware) // 2. Second
export class UserController {
  @Post('/', EndpointMiddleware) // 3. Third
  createUser() {
    // 4. Finally the controller method
  }
}

Released under the AGPL v3 License.