LogoRevali

Overview

Reusable classes to handle incoming requests

Lifecycle components are classes that are used to manage the lifecycle of a request. Specifically, they are used to manage the request from the time it is received by the server to the time it is sent back to the client. Each can have dedicated tasks, such as logging, authentication, or authorization.

Built-in kits#

KitUsage
@RequestId() Ensure X-Request-Id (or a custom header) on every request
@AllowOrigins(...) CORS allowed origins (access control, not a LifecycleComponent)

App Lifecycle#

Since Lifecycle Components are used for requests, they are called upon when a request is received by the server.

You can think of a basic request lifecycle as follows:

Request Middleware Controller Endpoint Middleware (reversed) Response
Request [ A, B, C ] MyController hello [ C, B, A ] Response

When a request is received by the server, it is passed to the middleware which performs its task. The middleware then passes the request to the controller's endpoint which will process the request and resolve the response to send. The response is then passed back to the middleware in reverse order. Finally, the response is then sent back to the client.

Exceptions#

If an exception is thrown during the request lifecycle, the flow will be aborted and the exception will be caught by the server. The server will then send an error response back to the client.

Lifecycle Order#

  1. Request
  2. Request Wrapper (pre)
  3. Observer (pre)
  4. Middleware
  5. Guard
  6. Interceptor (Pre)
  7. Pipes
  8. Endpoint
  9. Interceptor (Post)
  10. Request Wrapper (post)
  11. Observer (post)
  12. Response

Scoping#

Lifecycle components can be applied at different levels of the application: app, controller, and endpoint. By applying Lifecycle Components at different levels, you can control when the Lifecycle Component gets executed.

App Level#

To apply a Lifecycle Component to the entire application, you can annotate the app with the Lifecycle Component.

routes/apps/my_app.dart
import 'package:revali_router/revali_router.dart';

// highlight-next-line
@MyLifecycleComponent()
@App()
final class MyApp extends AppConfig {
    ...
}

Controller Level#

To apply a Lifecycle Component to a specific controller, you can annotate the controller with the Lifecycle Component.

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

// highlight-next-line
@MyLifecycleComponent()
@Controller('')
class MyController {
    ...
}

Endpoint Level#

To apply a Lifecycle Component to a specific endpoint, you can annotate the endpoint with the Lifecycle Component.

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

@Controller('')
class MyController {
    ...

    @Get()
    // highlight-next-line
    @MyLifecycleComponent()
    String hello() {
        return 'world';
    }
}

Order of Execution#

The order of execution of Lifecycle Components is important. When a request is received by the server, the middleware is executed in the order that they are applied. The controller is then executed, followed by the endpoint. The response is then passed back to the middleware in reverse order. Understanding the order of execution of Lifecycle Components is important when designing your application.

If we have multiple Lifecycle Components applied to an endpoint, the order of execution is from top to bottom.

routes/apps/my_app.dart
@LifecycleComponent0()
@App()
final class MyApp extends AppConfig {
    ...
}
routes/controllers/my_controller.dart
@LifecycleComponentA()
@Controller('')
class MyController {

    @Get()
    @LifecycleComponentB()
    @LifecycleComponentC()
    String hello() {
        return 'world';
    }
}

In the example above, the order of Lifecycle Component execution is as follows:

  1. LifecycleComponent0
  2. LifecycleComponentA
  3. LifecycleComponentB
  4. LifecycleComponentC
  5. -- endpoint --
  6. LifecycleComponentC
  7. LifecycleComponentB
  8. LifecycleComponentA
  9. LifecycleComponent0

Error Responses#

Some Lifecycle Components are responsible for returning error responses. Such components include ExceptionCatcher, Guard, and Middleware.

Typically, a Lifecycle Component that can return an error response can accept a statusCode, headers, and body. The status code and body values passed to the method will override any values previously set by the request flow, while the headers will be merged with the headers set by the request flow.

Debug Mode#

When an error response is returned in debug mode, a stack trace will be included in the error response.

Depending on the response content type, the stack trace will be formatted differently.

String Debug Message

body: 'An error occurred',
An error occurred

__DEBUG__:
Error: Instance of 'MyException'

Stack Trace:
routes/hello_controller.dart 13:5                            HelloController.hello
.revali/server/routes/__hello.dart 15:16                     hello.<fn>
package:revali_router/src/router/execute.dart 56:22          Execute.run.<fn>
dart:async                                                   runZonedGuarded
package:revali_router/src/router/execute.dart 54:11          Execute.run
package:revali_router/src/router/router.dart 221:12          Router._handle
package:revali_router/src/router/router.dart 190:22          Router.handle
package:revali_router/src/server/handle_requests.dart 23:20  handleRequests

Map Debug Message

body: {
  'message': 'An error occurred',
},
{
  "message": "An error occurred",
  "__DEBUG__": {
    "error": "Instance of 'MyException'",
    "stackTrace": [
      "routes/hello_controller.dart 13:5                            HelloController.hello",
      ".revali/server/routes/__hello.dart 15:16                     hello.<fn>",
      "package:revali_router/src/router/execute.dart 56:22          Execute.run.<fn>",
      "dart:async                                                   runZonedGuarded",
      "package:revali_router/src/router/execute.dart 54:11          Execute.run",
      "package:revali_router/src/router/router.dart 221:12          Router._handle",
      "package:revali_router/src/router/router.dart 190:22          Router.handle",
      "package:revali_router/src/server/handle_requests.dart 23:20  handleRequests"
    ]
  }
}

List Debug Message

body: [
  'An error occurred',
],
[
  "An error occurred",
  {
    "__DEBUG__": {
      "error": "Instance of 'MyException'",
      "stackTrace": [
        "routes/hello_controller.dart 13:5                            HelloController.hello",
        ".revali/server/routes/__hello.dart 15:16                     hello.<fn>",
        "package:revali_router/src/router/execute.dart 56:22          Execute.run.<fn>",
        "dart:async                                                   runZonedGuarded",
        "package:revali_router/src/router/execute.dart 54:11          Execute.run",
        "package:revali_router/src/router/router.dart 159:22          Router.handle",
        "package:revali_router/src/server/handle_requests.dart 23:20  handleRequests"
      ]
    }
  }
]

Profile Mode#

When an error response is return in profile mode, the error response will not include any debug messages;

Release Mode#

When an error response is returned in release mode, the error response will not include any debug messages.