From Dart Frog to Serinus
This guide is for Dart Frog users who want to migrate their applications to Serinus. But first, let's compare the two frameworks.
Dart Frog is a server-side framework for Dart built on top of Shelf, focused on a simple developer experience with file-based routing and middleware-driven dependency injection.
Serinus, on the other hand, is a modular backend framework for Dart that provides built-in structure for routing, dependency injection, hooks, metadata, validation, and typed request body parsing.
Routing
Dart Frog uses file-based routing where each endpoint maps to files in a routes directory. This is straightforward for small projects, but route organization can become harder to manage as the application grows.
Serinus groups routes inside controllers and uses route definitions with explicit HTTP method factories. This keeps route structure centralized and easier to evolve.
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context) async {
switch (context.request.method) {
case HttpMethod.get:
return Response(body: 'Hello, World!');
default:
return Response(statusCode: 405);
}
}Parameterized Routes
In Dart Frog, parameterized endpoints are represented as dynamic route files such as [id].dart.
In Serinus, parameterized routes are defined directly in the same controller using path parameters.
import 'package:dart_frog/dart_frog.dart';
// routes/posts/index.dart
Future<Response> onRequest(RequestContext context) async {
return Response(body: 'post list');
}
// routes/posts/[id].dart
Response onRequest(RequestContext context, String id) {
return Response(body: 'post id: $id');
}Dependency Injection
Dart Frog supports dependency injection through middleware and dependency access with context.read<T>() and their values are created per request and not shared across requests unless you use a global variable or a singleton pattern.
Serinus supports dependency injection through Providers declared on a Module, with dependencies consumed via context.use<T>().
import 'package:dart_frog/dart_frog.dart';
Handler middleware(Handler handler) {
return handler.use(provider<String>((context) => 'Welcome to Dart Frog!'));
}
Future<Response> onRequest(RequestContext context) async {
final greeting = context.read<String>();
return Response(body: greeting);
}Hooks and Metadata
Serinus includes hooks and route metadata for request-lifecycle logic and route-level behavior tagging.
Dart Frog does not provide an equivalent built-in metadata system for route behavior declarations.
// No built-in hook + metadata system.
// Similar behavior is usually modeled
// with middleware and custom conventions.Interoperability with Shelf
Dart Frog is built on Shelf, so Shelf middleware and ecosystem packages are naturally available.
Serinus is not built on Shelf, but supports interoperability with Shelf middleware to ease migration and reuse existing tooling.
import 'package:shelf/shelf.dart';
Handler middleware(Handler handler) {
return handler.use((inner) => logRequests()(inner));
}Validation
Dart Frog does not include built-in request validation primitives, so validation is generally implemented with custom code or third-party packages.
Serinus provides built-in validation powered by Acanthis, allowing validation of request data before it reaches handlers.
import 'dart:convert';
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context) async {
final payload = await context.request.body();
final data = jsonDecode(payload) as Map<String, dynamic>;
final name = data['name'] as String?;
if (name == null || name.isEmpty) {
return Response.json(statusCode: 400, body: {'error': 'Name is required'});
}
return Response.json(body: {'message': 'User $name created'});
}Typed Responses and Body Parsing
Dart Frog handlers return Response objects and body parsing is handled manually within the handler.
Serinus supports typed request body parsing and typed handler signatures for clearer contracts.
import 'dart:convert';
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context) async {
final payload = await context.request.body();
final data = jsonDecode(payload) as Map<String, dynamic>;
return Response.json(body: {'message': 'Hello ${data['name']}'});
}INFO
Typed body parsing code generation requires serinus_cli with serinus generate models.
Conclusion
Both Dart Frog and Serinus are strong options for building backend services in Dart.
If you prefer a file-first approach with close Shelf alignment, Dart Frog is a solid choice. If you prefer a structured, modular architecture with built-in hooks, metadata, validation, and typed body parsing, Serinus provides those capabilities out of the box.
