Skip to content

Migrate from 1.x to 2.x

This guide outlines the steps required to migrate your application to the latest version of Serinus. The framework has undergone significant refactoring to improve type safety, consistency, and modularity.

Provider & Module Refactoring

Rename DeferredProvider to ComposedProvider

The way we handle providers that depend on other providers has changed to a "composition" model.

  • Update Provider.deferred to Provider.composed.
  • Update DeferredProvider to ComposedProvider.
  • Update the factory function to use CompositionContext and the context.use<T>() syntax.
dart
class AppModule extends Module {
  
    AppModule(): super(
        providers: [
            Provider.deferred( 
                (AppProvider appProvider) => SecondProvider(appProvider),
                inject: [AppProvider],
                type: SecondProvider 
            ),
            Provider.composed<SecondProvider>( 
                (CompositionContext context) async => SecondProvider(context.use<AppProvider>()),
                inject: [AppProvider],
            ) 
        ]
    )

}

Global Scoping

Global registration has moved from the Provider level to the Module level.

  • Remove isGlobal from your Provider classes.
  • Add isGlobal => true to the Module that should export its providers globally.
dart
class TestProvider extends Provider {

    @override
    bool get isGlobal => true;

}

class TestModule extends Module {

    @override
    bool get isGlobal => true;

}

Asyncrhonous Modules

The registerAsync method must now explicitly return a DynamicModule.

dart
class AppModule extends Module {

    Future<DynamicModule> registerAsync() async {
        return DynamicModule(
            imports: [
                // other modules
            ],
            providers: [
                // providers
            ]
        );
    }

}

Controllers & Routing

Required Path Parameters

Controllers now require an explicit path in the constructor.

  • Old: super(path: '/')
  • New: super('/')
dart
class AppController extends Controller {
  
    AppController() : super(path: '/');
    AppController() : super('/');

}

Strict handler definitions

Route handlers must now follow a strict signature: Future<T> Function(RequestContext context). This ensures better type safety and consistency.

  • Ensure all handlers are async and return a Future.
  • Custom parameters (like path params) can no longer be passed directly into the handler function; they must be accessed via context.

Route hooks

Routes no longer have lifecycle hooks. If you need route-specific logic, add hooks directly to the route's hooks list.

Request & Response handling

Unified Execution Context

Middlewares, Pipes and Hooks now use ExecutionContext instead of raw request/response objects. This allows the same logic to work across HTTP, WebSockets, and other protocols.

  • Use context.argumentsHost to access the specific protocol host (e.g., HttpArgumentsHost).

Body Parsing Rework

The ParseSchema pipe and schema parameters in routes have been removed.

dart
on(
    Route.post(
        '/data', 
        pipes: {} 
    ),
    schema: AcanthisParseSchema(),
    (context) async {
        return data;
    }
);
  • Use the new context.bodyAs<T>() method to parse bodies into a specific type T.
  • Standardize on using Pipes for any custom validation logic.
dart
on(Route.post('/json'), (context) async {
    final body = context.bodyAs<Map<String, dynamic>>(); // to parse the body as JSON
    return body;
});
on(Route.post('/text'), (context) async {
    final body = context.bodyAs<String>(); // to parse the body as plain text
    return body;
});
on(Route.post('/form'), (context) async {
    final body = context.bodyAs<FormData>(); // to parse the body as form data
    return body;
});
  • Headers: now handled via a dedicated Headers class for better structure.
  • Status Codes: POST requests now return 201 Created by default instead of 200 OK.

Middleware & Hooks

Fluent Middleware API

Middlewares are no longer registered in the Module constructor.

  • Implement the configure(MiddlewareConsumer consumer) method in your module.
  • Use the fluent .apply().forRoutes() syntax.
dart
class AppModule extends Module {

    AppModule() : super(
        middlewares: [ 
            LogMiddleware( 
                routes: ['*'] 
            ) 
        ] 
    );

    void configure(MiddlewareConsumer consumer) { 
        consumer.apply(LogMiddleware()).forRoutes([ 
            RouteInfo( 
                '*'
            ) 
        ]);
    } 

}

Hook Separation

The OnRequestResponse mixin has been split.

  • Use OnRequest and OnResponse separately.
  • Update method signatures to use ExecutionContext and WrappedResponse where applicable.
dart
Future<void> onRequest(Request request, InternalResponse response);
Future<void> onRequest(ExecutionContext context);

Future<void> onResponse(Request request, dynamic data, ResponseProperties properties);
Future<void> onResponse(ExecutionContext context, WrappedResponse data);

Future<void> afterHandle(RequestContext context, dynamic response);
Future<void> afterHandle(ExecutionContext context, WrappedResponse response);

Future<void> beforeHandle(RequestContext context);
Future<void> beforeHandle(ExecutionContext context);

Utilities & Services

Logger Refactor

The logging API has been updated for better flexibility.

  • Rename loggerService to logger.
  • Change loggingLevel (single value) to logLevels (a Set<LogLevel>).
dart
void main(List<String> arguments) async {
  final application = await serinus.createApplication(
      entrypoint: AppModule(),
      host: InternetAddress.anyIPv4.address,
      loggerService: null,
      loggingLevel: LogLevel.info 
      logger: ConsoleLogger(prefix: 'Serinus New Logger'),
      logLevels: {LogLevel.info} 
    );
  await application.serve();
}

Exceptions

The message parameter in SerinusException is now a required positional argument rather than a named one.

  • New: throw BadGatewayException('Message').
dart
throw BadGatewayException(message: 'Failed to retrieve template');
throw BadGatewayException('Failed to retrieve template');

View Engine Simplification

  • View and ViewString are merged into one View class.
  • Use View.template() or View.string() constructors.
  • View Engines now only need to implement a single render method.
dart
class MustacheViewEngine extends ViewEngine {

    Future<String> render(View view) async {}
    Future<String> renderString(ViewString view) async {} 

}

// Usage in controller
class AppController extends Controller {
  
    AppController() : super('/') {
        on(Route.get('/template'), (context) async {
            return View('template', {});
            return View.template('templateName', {});
        });
        on(Route.get('/string'), (context) async {
            return View('string', {});
            return View.string('string', {});
        });
    }

}

Deprecations & Removals

  • Tracer API: Removed (to be reintroduced later).
  • ModelProvider: Now uses String keys instead of Type for registration.
  • ResponseProperties: Renamed to ResponseContext.

© 2025 Francesco Vallone. Built with 💙 and Dart 🎯 | One of the 🐤 of Avesbox.