Skip to content

From Shelf to Serinus

This guide is for Shelf users who want to migrate their applications to Serinus. But first, let's compare the two frameworks.

Shelf is a minimalistic web server framework for Dart that provides basic functionalities for handling HTTP requests and responses. It is designed to be lightweight and flexible, allowing developers to build web applications with minimal overhead.

Serinus, on the other hand, is a full-featured backend framework for Dart that provides a wide range of functionalities for building scalable and maintainable web applications. It is designed to be modular and extensible, allowing developers to build complex applications with ease.

Routing

Serinus and Shelf takes two different stances when it comes to routing. By default, Shelf does not provide a built-in routing mechanism, leaving it up to the developer to implement their own routing logic or use third-party packages. Serinus, instead, provides a powerful and flexible routing system out of the box, allowing developers to define routes using decorators and organize them into controllers and modules.

INFO

In the example below we will compare Serinus with Shelf using the shelf_router package for routing capabilities. To keep the comparison fair.

dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';

final router = Router();

router.get(
  '/hello', 
  (Request request) {
    return Response.ok('Hello, World!');
  }
);

router.get(
  '/users/<id>', 
  (Request req, String id) {
    return Response.ok('User $id');
  }
);

Handler handler = const Pipeline()
    .addHandler(router);
Shelf uses composable handlers to define routes, which can lead to more boilerplate code as the application grows.

Handlers

In Shelf, handlers are simply functions that take a Request object and return a Response object. In Serinus, handlers are methods within controllers that take a RequestContext object and return a response, which can be of various types (e.g., Map, List, custom objects) and will be automatically serialized to the appropriate format and content type.

Also you can access the same data from the Request object in Shelf using the RequestContext object in Serinus, but with additional features and utilities provided by the framework.

dart
Response helloHandler(Request request) {
  final limit = request.url.queryParameters['limit'];
  return Response.ok('Hello, World!');
}
Shelf handlers are simple functions that return a `Response` object.

Body Parsing

Shelf does not provide built-in body parsing capabilities, so developers need to manually parse the request body or use third-party packages. Serinus, on the other hand, provides automatic body parsing for various content types (e.g., JSON, form data) and makes it easy to access the parsed data within the request context.

dart
Future<Response> createUserHandler(
  Request request
) async {
  final payload = await request.readAsString();
  final data = jsonDecode(payload);
  final name = data['name'];
  return Response.ok('User $name created');
}
Shelf requires manual parsing of the request body.

Middlewares

Shelf provides a single type of middleware that can be used to intercept and modify requests and responses. Serinus, on the other hand, provides a more flexible middleware system that allows developers to create custom middlewares for various purposes, such as authentication, logging, error handling, and more.

Right now, Serinus allows you to create common middlewares that can be applied globally, to specific controllers, routes and on a specific module. But it also allows you to separate responsabilities to more specific variants of middlewares, such as Hooks, Exception Filters, and Pipes. You can also use Shelf middlewares in Serinus applications.

dart
Middleware logMiddleware = (
  Handler innerHandler
) {
  return (Request request) async {
    print('Request: ${request.method} ${request.url}');
    final response = await innerHandler(
      request
    );
    print('Response: ${response.statusCode}');
    return response;
  };
};
Shelf uses a single middleware type for request and response interception.

Error Handling

Shelf requires developers to manually handle errors within their handlers or middlewares. Serinus provides a built-in exception handling mechanism that allows developers to create custom exception filters to handle specific types of exceptions and return appropriate responses or by using the global exception filter provided by the framework.

dart
Future<Response> handler(
  Request request
) async {
  try {
    // Handler logic
  } catch (e) {
    return Response.internalServerError(
      body: 'Internal Server Error'
    );
  }
}
Shelf requires manual error handling in handlers.

Validation

Shelf does not provide built-in validation capabilities, so developers need to manually validate request data or use third-party packages. Serinus provides a powerful validation system that allows developers to define validation rules using decorators and automatically validate request data before it reaches the handler.

dart
Future<Response> createUserHandler(
  Request request
) async {
  final payload = await request.readAsString();
  final data = jsonDecode(payload);
  final name = data['name'];
  if (name == null || name.isEmpty) {
    return Response(
      400, 
      body: 'Name is required'
    );
  }
  return Response.ok('User $name created');
}
Shelf requires manual validation of request data making error-prone code and less reusable.

OpenAPI Integration

Shelf does not provide built-in OpenAPI integration, so developers need to manually document their APIs or use third-party packages. Serinus provides automatic OpenAPI documentation generation based on the defined controllers, routes, and DTOs, making it easy to keep the documentation up-to-date with the codebase.

dart
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_swagger_ui/shelf_swagger_ui.dart';

void main(List<String> args) async {
  final path = 'specs/swagger.yaml';
  final handler = SwaggerUI.fromFile(
    File(path), 
    title: 'Swagger Test'
  );
  var server = await io.serve(
    handler, 
    '0.0.0.0', 
    4001
  );
  print('Serving at http://${server.address.host}:${server.port}');
}
Shelf requires manual OpenAPI documentation generation.

Conclusion

Serinus offers a more comprehensive and feature-rich framework for building web applications compared to Shelf. With its built-in routing, automatic body parsing, flexible middleware system, built-in error handling, powerful validation, and automatic OpenAPI documentation generation, while Shelf provides a minimalistic and flexible approach that requires more manual implementation and third-party packages to achieve similar functionalities.

If you are looking for a full-featured backend framework that can help you build scalable and maintainable web applications with ease, Serinus is the way to go.

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