Khadem Response System
Modular HTTP response handling with JSON, files, streaming, and comprehensive header management.
Basic Response Methods
dart
// Basic response methods
server.get('/api/status', (req, res) async {
res.sendJson({'status': 'ok', 'timestamp': DateTime.now()});
});
server.get('/text', (req, res) async {
res.send('Hello World');
});
server.get('/html', (req, res) async {
res.html('<h1>Welcome</h1><p>This is HTML content</p>');
});
server.get('/empty', (req, res) async {
res.noContent(); // 204 No Content
});
// Status code convenience methods
server.get('/success', (req, res) async {
res.ok().sendJson({'message': 'Success'});
});
server.get('/created', (req, res) async {
res.created().sendJson({'id': 123, 'message': 'Resource created'});
});
server.get('/error', (req, res) async {
res.badRequest().sendJson({'error': 'Invalid request'});
});
Response Types
sendJson()
- JSON responsessend()
- Plain texthtml()
- HTML contentfile()
- File downloadsstream()
- Data streamingbytes()
- Binary dataStatus Code Methods
ok()
- 200 OKcreated()
- 201 CreatednoContent()
- 204 No ContentbadRequest()
- 400 Bad Requestunauthorized()
- 401 UnauthorizednotFound()
- 404 Not FoundJSON Responses
dart
// JSON responses with different formats
server.get('/api/user/:id', (req, res) async {
final user = await User.find(req.param('id'));
if (user == null) {
return res.notFound().sendJson({
'error': 'User not found',
'code': 'USER_NOT_FOUND'
});
}
res.sendJson({
'id': user.id,
'name': user.name,
'email': user.email,
'created_at': user.createdAt.toIso8601String()
});
});
// Pretty-printed JSON for debugging
server.get('/api/debug', (req, res) async {
final debugData = {
'config': Khadem.config.all(),
'routes': server.routes.length,
'timestamp': DateTime.now()
};
res.jsonPretty(debugData, indent: 2);
});
// Paginated API response
server.get('/api/users', (req, res) async {
final page = int.tryParse(req.query['page'] ?? '1') ?? 1;
final limit = int.tryParse(req.query['limit'] ?? '10') ?? 10;
final users = await User.paginate(page, limit);
final total = await User.count();
res.sendJson({
'data': users,
'pagination': {
'page': page,
'limit': limit,
'total': total,
'pages': (total / limit).ceil()
}
});
});
JSON Features
- Automatic Content-Type header
- Pretty printing with
jsonPretty()
- Proper JSON encoding
- Support for nested data structures
File & Binary Responses
dart
// File download with automatic MIME type detection
server.get('/download/avatar/:filename', (req, res) async {
final filename = req.param('filename');
final file = File('storage/avatars/$filename');
if (!await file.exists()) {
return res.notFound().sendJson({'error': 'File not found'});
}
await res.file(file);
});
// File download with custom headers
server.get('/export/report.pdf', (req, res) async {
final file = File('storage/reports/monthly.pdf');
res.header('Content-Disposition', 'attachment; filename="monthly-report.pdf"');
res.header('Content-Description', 'Monthly Report');
await res.file(file);
});
// Image serving with caching
server.get('/images/:filename', (req, res) async {
final filename = req.param('filename');
final file = File('storage/images/$filename');
if (!await file.exists()) {
return res.notFound().sendJson({'error': 'Image not found'});
}
// Cache images for 1 hour
res.cache('public, max-age=3600');
await res.file(file);
});
File Response Features
- Automatic MIME type detection
- Content-Length header
- Download with custom filename
- Efficient streaming
Binary Data
dart
// Binary data response
server.get('/api/binary', (req, res) async {
final data = await generateBinaryData();
res.bytes(data, contentType: 'application/octet-stream');
});
// Custom content type
server.get('/api/pdf', (req, res) async {
final pdfBytes = await generatePdf();
res.bytes(pdfBytes, contentType: 'application/pdf');
});
Streaming Responses
dart
// Database streaming
server.get('/api/export/users', (req, res) async {
res.header('Content-Type', 'application/json');
res.header('Transfer-Encoding', 'chunked');
final userStream = Database.query('SELECT * FROM users')
.asStream()
.map((user) => jsonEncode(user) + '\n');
await res.stream(userStream);
});
// Real-time progress updates
server.get('/api/process/:taskId', (req, res) async {
final taskId = req.param('taskId');
res.header('Content-Type', 'text/event-stream');
res.header('Cache-Control', 'no-cache');
final progressStream = Stream.periodic(
Duration(seconds: 1),
(count) => 'data: {"taskId": "$taskId", "progress": ${count * 10}}\n\n'
).take(10);
await res.stream(progressStream);
});
// Large file streaming
server.get('/download/large-file.mp4', (req, res) async {
final file = File('storage/videos/large.mp4');
res.header('Content-Type', 'video/mp4');
res.header('Accept-Ranges', 'bytes');
await res.stream(file.openRead());
});
// Custom data transformation
server.get('/api/logs/stream', (req, res) async {
final logFile = File('storage/logs/app.log');
res.header('Content-Type', 'text/plain');
final logStream = logFile.openRead()
.transform(utf8.decoder)
.transform(LineSplitter())
.map((line) => '[LOG] $line\n');
await res.stream(logStream);
});
Streaming Benefits
- Memory efficient for large datasets
- Real-time data transmission
- Server-sent events support
- Custom data transformation
Headers & CORS
dart
// Custom headers
server.get('/api/data', (req, res) async {
// Set custom headers
res.header('X-API-Version', '1.0.0');
res.header('X-Request-ID', generateRequestId());
res.header('X-Response-Time', DateTime.now().millisecondsSinceEpoch.toString());
res.sendJson({'data': 'response with custom headers'});
});
// Content-Type and Content-Length
server.get('/api/file-info', (req, res) async {
final file = File('storage/document.pdf');
res.header('Content-Type', 'application/pdf');
res.header('Content-Length', (await file.length()).toString());
res.header('Content-Disposition', 'inline; filename="document.pdf"');
await res.file(file);
});
CORS Headers
dart
// CORS configuration
server.get('/api/public', (req, res) async {
res.cors(
allowOrigin: '*',
allowMethods: 'GET, POST',
allowHeaders: 'Content-Type, Authorization'
);
res.sendJson({'message': 'Public API'});
});
// Restricted CORS
server.get('/api/private', (req, res) async {
res.cors(
allowOrigin: 'https://myapp.com',
allowMethods: 'GET, POST, PUT, DELETE',
allowHeaders: 'Content-Type, Authorization, X-API-Key',
allowCredentials: true,
maxAge: 86400 // 24 hours
);
res.sendJson({'message': 'Private API'});
});
Security Headers
dart
// Security headers
server.get('/api/secure', (req, res) async {
res.security(
enableHsts: true,
enableCsp: true,
enableXFrameOptions: true,
enableXContentTypeOptions: true,
cspPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'"
);
res.sendJson({'message': 'Secure response'});
});
// Individual security headers
server.get('/api/headers', (req, res) async {
res.header('X-Frame-Options', 'DENY');
res.header('X-Content-Type-Options', 'nosniff');
res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.header('Content-Security-Policy', "default-src 'self'");
res.sendJson({'message': 'Response with security headers'});
});
Caching & Performance
dart
// Cache control examples
server.get('/api/static-data', (req, res) async {
// Cache for 5 minutes
res.cache('public, max-age=300');
res.sendJson({'data': 'This can be cached'});
});
server.get('/api/dynamic-data', (req, res) async {
// No caching
res.noCache();
res.sendJson({'data': 'This should not be cached'});
});
server.get('/api/user-data', (req, res) async {
// Private cache (user-specific)
res.cache('private, max-age=60');
res.sendJson({'data': 'User-specific data'});
});
// Static file caching
server.get('/assets/style.css', (req, res) async {
// Cache for 1 year
res.cache('public, max-age=31536000, immutable');
await res.file(File('public/assets/style.css'));
});
// Conditional caching
server.get('/api/posts/:id', (req, res) async {
final post = await Post.find(req.param('id'));
final lastModified = post.updatedAt;
res.header('Last-Modified', lastModified.toUtc().toString());
res.cache('public, max-age=3600'); // 1 hour
res.sendJson({'post': post});
});
Cache Strategies
Static Assets
Long-term caching
API Data
Short-term caching
Dynamic Content
No caching
Session & Flash Messages
dart
// Session management
server.post('/login', (req, res) async {
final credentials = await req.body;
final user = await authenticateUser(credentials);
if (user != null) {
// Store user data in session
res.sessionPut('user_id', user.id);
res.sessionPut('user_name', user.name);
res.sessionPut('user_role', user.role);
// Flash success message
res.flashSession('success', 'Login successful!');
res.sendJson({'message': 'Login successful'});
} else {
res.unauthorized().sendJson({'error': 'Invalid credentials'});
}
});
// Flash input preservation
server.post('/register', (req, res) async {
final data = await req.body;
try {
final user = await User.create(data);
res.flashSession('success', 'Account created successfully!');
res.sendJson({'message': 'Account created'});
} catch (e) {
// Preserve form input for repopulation
res.flashInput(data);
res.flashSession('error', 'Failed to create account');
res.badRequest().sendJson({'error': 'Validation failed'});
}
});
// Access flash messages
server.get('/messages', (req, res) async {
final successMessage = req.session.pull('success');
final errorMessage = req.session.pull('error');
res.sendJson({
'messages': {
if (successMessage != null) 'success': successMessage,
if (errorMessage != null) 'error': errorMessage,
}
});
});
Session Features
- Store data across requests
- Flash messages for one-time notifications
- Old input preservation for form validation
- Automatic cleanup of flash data
Error Handling
dart
// Error response patterns
server.get('/api/user/:id', (req, res) async {
final userId = req.param('id');
if (userId == null || int.tryParse(userId) == null) {
return res.badRequest().sendJson({
'error': {
'code': 'INVALID_USER_ID',
'message': 'User ID must be a valid integer',
'field': 'id',
'received': userId
}
});
}
final user = await User.find(int.parse(userId));
if (user == null) {
return res.notFound().sendJson({
'error': {
'code': 'USER_NOT_FOUND',
'message': 'User with ID $userId not found',
'user_id': userId
}
});
}
res.sendJson({'user': user});
});
// Validation errors
server.post('/api/posts', (req, res) async {
final data = await req.body;
final errors = <String, String>{};
if (data['title'] == null || data['title'].isEmpty) {
errors['title'] = 'Title is required';
}
if (data['content'] == null || data['content'].isEmpty) {
errors['content'] = 'Content is required';
}
if (errors.isNotEmpty) {
return res.badRequest().sendJson({
'error': {
'code': 'VALIDATION_FAILED',
'message': 'Please fix the following errors',
'fields': errors
}
});
}
final post = await Post.create(data);
res.created().sendJson({'post': post});
});
// Global error handler
server.get('/test-error', (req, res) async {
try {
// Simulate an error
throw Exception('Database connection failed');
} catch (e) {
print('Error: $e');
res.internalServerError().sendJson({
'error': {
'code': 'INTERNAL_ERROR',
'message': 'An unexpected error occurred',
'trace_id': generateTraceId(),
'details': Khadem.env.get('APP_DEBUG') == 'true' ? e.toString() : null
}
});
}
});
Error Response Best Practices
- Use appropriate HTTP status codes
- Provide consistent error format
- Include error codes for API consumers
- Don't expose sensitive information
- Log errors for debugging
Advanced Patterns
Response Builder
dart
// Response builder pattern
class ApiResponse {
final Response _response;
ApiResponse(this._response);
ApiResponse success(dynamic data, {String? message}) {
_response.sendJson({
'success': true,
'data': data,
if (message != null) 'message': message,
'timestamp': DateTime.now().toIso8601String()
});
return this;
}
ApiResponse error(String message, {
String? code,
int statusCode = 400,
Map<String, dynamic>? details
}) {
_response.statusCode(statusCode).sendJson({
'success': false,
'error': {
'message': message,
if (code != null) 'code': code,
if (details != null) 'details': details,
},
'timestamp': DateTime.now().toIso8601String()
});
return this;
}
ApiResponse created(dynamic data) {
_response.statusCode(201).sendJson({
'success': true,
'data': data,
'message': 'Resource created successfully'
});
return this;
}
}
// Usage in controllers
class UserController {
static Future index(Request req, Response res) async {
final users = await User.all();
ApiResponse(res).success(users);
}
static Future store(Request req, Response res) async {
try {
final data = await req.body;
final user = await User.create(data);
ApiResponse(res).created(user);
} catch (e) {
ApiResponse(res).error('Failed to create user', code: 'USER_CREATE_FAILED');
}
}
}
Consistent API responses with method chaining
Server-Sent Events
dart
// Server-Sent Events
server.get('/events/notifications', (req, res) async {
res.header('Content-Type', 'text/event-stream');
res.header('Cache-Control', 'no-cache');
res.header('Connection', 'keep-alive');
// Send initial connection message
res.send('data: {"type": "connected", "message": "Connection established"}\n\n');
// Stream notifications
final notificationStream = getNotificationStream(req.user.id)
.map((notification) => 'data: ${jsonEncode(notification)}\n\n');
await res.stream(notificationStream);
});
// Progress tracking
server.get('/upload/progress/:uploadId', (req, res) async {
final uploadId = req.param('uploadId');
res.header('Content-Type', 'text/event-stream');
res.header('Cache-Control', 'no-cache');
final progressStream = Stream.periodic(
Duration(milliseconds: 500),
(count) => 'data: {"uploadId": "$uploadId", "progress": ${count * 10}}\n\n'
).take(10);
await res.stream(progressStream);
});
Real-time communication with clients
Complete API Example
dart
// Complete API example with all response features
import 'package:khadem/khadem_dart.dart';
class ApiController {
static Future getUsers(Request req, Response res) async {
try {
final page = int.tryParse(req.query['page'] ?? '1') ?? 1;
final limit = int.tryParse(req.query['limit'] ?? '10') ?? 10;
final search = req.query['search'];
var query = User.query();
if (search != null && search.isNotEmpty) {
query = query.where('name', 'LIKE', '%$search%');
}
final users = await query.paginate(page, limit);
final total = await query.count();
// Set pagination headers
res.header('X-Total-Count', total.toString());
res.header('X-Page', page.toString());
res.header('X-Per-Page', limit.toString());
// Cache for 5 minutes
res.cache('private, max-age=300');
res.sendJson({
'data': users.map((user) => user.toJson()).toList(),
'pagination': {
'page': page,
'per_page': limit,
'total': total,
'total_pages': (total / limit).ceil(),
'has_next': page * limit < total,
'has_prev': page > 1,
}
});
} catch (e) {
res.internalServerError().sendJson({
'error': 'Failed to fetch users',
'code': 'USERS_FETCH_FAILED'
});
}
}
static Future createUser(Request req, Response res) async {
try {
final data = await req.body;
// Validate input
final validation = await req.validator.validateData(data, {
'name': 'required|min:2|max:100',
'email': 'required|email|unique:users,email',
'password': 'required|min:8',
});
if (!validation.isValid) {
return res.badRequest().sendJson({
'error': 'Validation failed',
'code': 'VALIDATION_ERROR',
'fields': validation.errors
});
}
final user = await User.create({
...data,
'password': hashPassword(data['password']),
'email_verified_at': null,
});
// Flash success message
res.flashSession('success', 'Account created successfully!');
res.created().sendJson({
'user': user.toJson(),
'message': 'Account created successfully',
'next_steps': [
'Check your email for verification',
'Complete your profile'
]
});
} catch (e) {
// Flash input for form repopulation
res.flashInput(await req.body);
res.flashSession('error', 'Failed to create account');
res.internalServerError().sendJson({
'error': 'Failed to create user',
'code': 'USER_CREATE_FAILED'
});
}
}
static Future uploadAvatar(Request req, Response res) async {
try {
final userId = req.param('id');
final avatarFile = req.file('avatar');
if (avatarFile == null) {
return res.badRequest().sendJson({
'error': 'No avatar file provided',
'code': 'MISSING_AVATAR'
});
}
// Validate file
if (avatarFile.size > 5 * 1024 * 1024) {
return res.badRequest().sendJson({
'error': 'File too large',
'code': 'FILE_TOO_LARGE'
});
}
final allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedTypes.contains(avatarFile.mimeType)) {
return res.badRequest().sendJson({
'error': 'Invalid file type',
'code': 'INVALID_FILE_TYPE'
});
}
// Save file
final filename = 'avatar_${userId}_${DateTime.now().millisecondsSinceEpoch}.${avatarFile.extension}';
await avatarFile.move('storage/avatars/$filename');
// Update user
final user = await User.find(userId);
await user.update({'avatar': filename});
res.sendJson({
'message': 'Avatar uploaded successfully',
'avatar_url': '/storage/avatars/$filename'
});
} catch (e) {
res.internalServerError().sendJson({
'error': 'Failed to upload avatar',
'code': 'AVATAR_UPLOAD_FAILED'
});
}
}
static Future streamLogs(Request req, Response res) async {
res.header('Content-Type', 'text/plain');
res.header('Cache-Control', 'no-cache');
final logFile = File('storage/logs/app.log');
final logStream = logFile.openRead()
.transform(utf8.decoder)
.transform(LineSplitter())
.map((line) => '[LOG] $line\n');
await res.stream(logStream);
}
}
// Register routes
void registerApiRoutes(Server server) {
server.group(
prefix: '/api/v1',
middleware: [AuthMiddleware()],
routes: (router) {
router.get('/users', ApiController.getUsers);
router.post('/users', ApiController.createUser);
router.post('/users/:id/avatar', ApiController.uploadAvatar);
router.get('/logs/stream', ApiController.streamLogs);
}
);
}
Best Practices
✅ Recommendations
- Use appropriate HTTP status codes for different scenarios
- Set proper Content-Type headers automatically
- Implement consistent error response formats
- Use streaming for large responses to save memory
- Set appropriate cache headers for performance
- Validate response data before sending
- Use flash messages for user feedback
❌ Avoid
- Don't send multiple responses in one handler
- Don't expose sensitive error details in production
- Don't use generic error messages
- Don't forget to set proper status codes
- Don't send HTML when JSON is expected
- Don't block the event loop with synchronous operations