LogoRevali

Middleware

Prepare a request before guards and the endpoint run - load data, set headers - or stop it early.

Middleware runs before guards and the endpoint. Use it to prepare the request, for example by loading the current user into Data or setting a response header. It can also stop the request early. If the only job is to allow or deny access, write a guard instead.

Example#

lib/components/tenant.dart
import 'package:revali_router/revali_router.dart';

class Tenant implements LifecycleComponent {
  const Tenant();

  MiddlewareResult resolve(@Header('X-Tenant') String? tenant, Data data) {
    if (tenant == null) {
      return const MiddlewareResult.stop(body: 'X-Tenant header is required');
    }

    data.add(TenantId(tenant));

    return const MiddlewareResult.next();
  }
}

class TenantId {
  const TenantId(this.value);

  final String value;
}
routes/controllers/orders_controller.dart
import 'package:revali_router/revali_router.dart';

@Tenant()
@Controller('orders')
class OrdersController {
  const OrdersController();

  @Get()
  String list(@Data() TenantId tenant) => 'orders for ${tenant.value}';
}
curl -H 'X-Tenant: acme' http://localhost:8080/api/orders
# 200 {"data":"orders for acme"}

curl http://localhost:8080/api/orders
# 400 X-Tenant header is required

In debug mode the 400 body also has a __DEBUG__ block appended.

Results#

ResultEffect
MiddlewareResult.next() Continue to the next middleware, then to the guards.
MiddlewareResult.stop({statusCode, headers, body}) End the request. The status defaults to 400.

The method can be async and return Future<MiddlewareResult>. stop takes the same arguments as the other error results. See Error Responses.

Classic Style#

As an alternative, implement Middleware and its use method, which receives the whole Context:

lib/components/tenant_middleware.dart
import 'package:revali_router/revali_router.dart';

class TenantMiddleware implements Middleware {
  const TenantMiddleware();

  @override
  Future<MiddlewareResult> use(Context context) async {
    final tenant = context.request.headers.get('X-Tenant');
    if (tenant == null) {
      return const MiddlewareResult.stop();
    }

    context.data.add(TenantId(tenant));
    return const MiddlewareResult.next();
  }
}

Apply it with @TenantMiddleware(), or by type with @Middlewares([TenantMiddleware]).