The Data object provides a way to share data between different lifecycle components
during a single request. It uses type-based storage where the type serves as the key, allowing you to store and retrieve data by its class type.
Storing Data#
Use the add method to store data in the context. Each type can only have one instance stored at a time - adding a new instance of the same type will replace the previous one.
// Store a user object
data.add<User>(user);
// Store a request ID
data.add<String>('req-123');
// Store a custom type
data.add<RequestMetadata>(metadata);
Retrieving Data#
Use the get method to retrieve stored data. Returns null if no data of that type exists.
// Get a user object
final user = data.get<User>();
// Get a request ID
final requestId = data.get<String>();
// Check if data exists before using it
final user = data.get<User>();
if (user != null) {
print('User: ${user.name}');
}
Checking Data Existence#
Use the has method to check if data of a specific type exists, or contains to check for a specific value.
// Check if user data exists
if (data.has<User>()) {
final user = data.get<User>();
// Process user data
}
// Check if a specific value is stored
if (data.contains<String>('expected-value')) {
// Handle specific value case
}
Real-World Example#
Here's a common pattern: storing user data from a middleware and accessing it in a guard.
1. Store User Data in Middleware#
import 'package:revali_router/revali_router.dart';
class UserMiddleware implements LifecycleComponent {
const UserMiddleware({required this.userService});
final UserService userService;
Future<MiddlewareResult> loadUser(
Request request,
Data data,
) async {
final userId = request.pathParameters['userId'];
if (userId != null) {
final user = await userService.getUser(userId);
data.add<User>(user);
}
return const MiddlewareResult.next();
}
}
2. Access User Data in Guard#
import 'package:revali_router/revali_router.dart';
class UserGuard implements LifecycleComponent {
const UserGuard();
GuardResult checkUser(Data data) {
final user = data.get<User>();
if (user == null) {
return const GuardResult.block(
statusCode: 401,
body: 'User not authenticated',
);
}
return const GuardResult.pass();
}
}
3. Use in Controller#
import 'package:revali_router/revali_router.dart';
@UserMiddleware()
@UserGuard()
@Controller('admin')
class AdminController {
@Get('users')
// User data is available from the middleware
Future<List<User>> getUsers(@Data() User currentUser) async {
return await userService.getAllUsers();
}
}
Best Practices#
Use Meaningful Types#
// ✅ Good - Clear, specific types
data.add<User>(user);
data.add<RequestId>(RequestId.generate());
data.add<SessionData>(sessionData);
// ❌ Avoid - Generic or unclear types
data.add<Object>(someObject); // Too generic
data.add<Map<String, dynamic>>(dataMap); // Unclear structure
Use Wrapper Classes for Primitives#
// ✅ Good - Type-safe primitives
class UserId {
const UserId(this.value);
final String value;
}
class RequestId {
const RequestId(this.value);
final String value;
}
data.add(UserId('123'));
data.add(RequestId('req-456'));
// ❌ Avoid - Raw primitives
data.add<String>('123'); // What kind of string?
data.add<String>('req-456'); // Ambiguous