LogoRevali

Components

Similar to creating a controller and endpoints using a class and methods, you can create lifecycle components. By using a class, you can group related lifecycle components together and reuse them across different controllers/endpoints.

Components are short-lived classes. They are created when the server is executing a particular piece of middleware, and they are destroyed when the middleware is done executing. New instances of the component are created for each request.

Group Lifecycle Components#

To create a group of lifecycle components, create a class that implements the LifecycleComponent class.

lib/components/my_component.dart
class MyComponent implements LifecycleComponent {
  const MyComponent();
}

You may add fields to this class as you need, such as classes from your dependencies or values that are specific to the component. You can use binding annotations to inject these dependencies into the component.

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

class MyComponent implements LifecycleComponent {
  const MyComponent(
    this.myService, {
        @Data() required this.role,
    });

  final MyService myService;
  final Role role;
}

Define a Lifecycle Component#

Now that you have a class to group your lifecycle components, you can create a lifecycle component by adding methods to the class. The method's return type is what determines which lifecycle component it associated with.

Return TypeLifecycle TypeFuture Support
WrapperResult Request Wrapper
GuardResult Guard
MiddlewareResult Middleware
InterceptorPreResult Interceptor (pre)
InterceptorPostResult Interceptor (post)
ExceptionCatcherResult<Exception> Exception Catcher
lib/components/my_component.dart
class MyComponent implements LifecycleComponent {
  const MyComponent();

   GuardResult getAuth() {
    // Perform authentication logic
  }

  Future<MiddlewareResult> getRole() async {
    // Get role logic
  }

  Future<GuardResult> verifyRole() async {
    // Perform role verification logic
  }
}

Binding#

Similar in endpoints, you can bind values to the parameters of the lifecycle component methods. Values such as the request, context, dependencies, or other lifecycle components.

lib/components/my_component.dart
class MyComponent implements LifecycleComponent {
  const MyComponent();

  GuardResult getAuth(@Body() Map<String, dynamic> body) {
    // Perform authentication logic
  }

  Future<GuardResult> verifyRole(@Param('id', UserPipe) User user) async {
    // Perform role verification logic
  }
}

Context#

Every Lifecycle Component method has access to the same Context, regardless of its role (Guard, Middleware, Interceptor, Exception Catcher, or Request Wrapper). There isn't a different context type per role -- Context exposes data, meta, route, request, response, and reflect, and each of those fields can be bound implicitly as its own parameter, so you don't need to add an annotation to bind it:

lib/components/my_component.dart
class MyComponent implements LifecycleComponent {
  const MyComponent();

  GuardResult getAuth(Data data) {
    final user = data.get<User>();

    if (user == null) {
      return const GuardResult.block(statusCode: 401);
    }

    return const GuardResult.pass();
  }

  Future<GuardResult> verifyRole(Request request) async {
    // Perform role verification logic using request.pathParameters, etc.
    return const GuardResult.pass();
  }
}

In addition to the base implied bindings, here's a comprehensive list of the implicit bindings available to every Lifecycle Component method:

Implicit BindingResolves To
ContextThe full context
DIThe app's dependency injection container
Requestcontext.request
RequestHeaderscontext.request.headers
RequestCookiescontext.request.headers.cookies
Responsecontext.response
Headerscontext.response.headers
ResponseHeaderscontext.response.headers
Cookiescontext.response.headers.cookies
ResponseCookiescontext.response.headers.cookies
SetCookiescontext.response.headers.setCookies
Body / PayloadBodycontext.response.body
Meta / MetaScopecontext.meta
RouteEntrycontext.route
Data context.data (see Data Sharing )
Reflectcontext.reflect
CleanUpA cleanup handle sourced from context.data

NextResponse is the one binding that is role-specific: a parameter typed NextResponse is what marks a method as a Request Wrapper (its return type must be WrapperResult).

Register the Lifecycle Component#

To register the lifecycle component, annotate your LifecycleComponent class on the app, controller, or endpoint level.

routes/controllers/my_controller.dart
import 'package:revali_router/revali_router.dart';

// highlight-next-line
@MyComponent()
@Get('')
Future<void> myEndpoint() {
    ...
}

Register as Type Reference#

routes/controllers/my_controller.dart
import 'package:revali_router/revali_router.dart';

// highlight-next-line
@LifecycleComponents([MyComponent])
@Get('')
Future<void> myEndpoint() {
    ...
}