Response System
Powerful HTTP response handling with Khadem's modular system. Send JSON, files, streams, and manage headers with an intuitive API.
Flexible Response API
Send various response types with proper headers, status codes, and caching. Built-in support for JSON, files, streaming, sessions, and more.
Basic Response Methods
Send different types of responses using simple, chainable methods with automatic header management.
// Basic response methods
router.get('/api/status', (req, res) async {
res.sendJson({'status': 'ok', 'timestamp': DateTime.now()});
});
router.get('/text', (req, res) async {
res.send('Hello World');
});
router.get('/html', (req, res) async {
res.html('<h1>Welcome</h1><p>This is HTML content</p>');
});
router.get('/empty', (req, res) async {
res.noContent(); // 204 No Content
});
// Status code convenience methods
router.get('/success', (req, res) async {
res.ok().sendJson({'message': 'Success'});
});
router.get('/created', (req, res) async {
res.created().sendJson({'id': 123, 'message': 'Resource created'});
});
router.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
Send JSON data with automatic content-type headers and proper encoding. Perfect for REST APIs.
// JSON responses with different formats
router.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
router.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
router.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
Serve files and binary data with automatic MIME type detection and efficient streaming.
// File download with automatic MIME type detection
router.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
router.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
router.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
// Binary data response
router.get('/api/binary', (req, res) async {
final data = await generateBinaryData();
res.bytes(data, contentType: 'application/octet-stream');
});
// Custom content type
router.get('/api/pdf', (req, res) async {
final pdfBytes = await generatePdf();
res.bytes(pdfBytes, contentType: 'application/pdf');
});Streaming Responses
Stream large datasets efficiently without loading everything into memory. Perfect for real-time data and large files.
// Database streaming
router.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
router.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
router.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
router.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
Manage custom headers, CORS policies, and security headers for your responses.
// Custom headers
router.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
router.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
// CORS configuration
router.get('/api/public', (req, res) async {
res.cors(
allowOrigin: '*',
allowMethods: 'GET, POST',
allowHeaders: 'Content-Type, Authorization'
);
res.sendJson({'message': 'Public API'});
});
// Restricted CORS
router.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
// Security headers
router.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
router.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
Optimize performance with proper cache control headers for different content types.
// Cache control examples
router.get('/api/static-data', (req, res) async {
// Cache for 5 minutes
res.cache('public, max-age=300');
res.sendJson({'data': 'This can be cached'});
});
router.get('/api/dynamic-data', (req, res) async {
// No caching
res.noCache();
res.sendJson({'data': 'This should not be cached'});
});
router.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
router.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
router.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
Long-term caching (1 year)
Short-term caching (5-60 min)
No caching
Session & Flash Messages
Store data across requests and display one-time flash messages for user feedback.
// Session management
router.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
router.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
router.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
Return consistent, informative error responses with proper status codes and error details.
// Error response patterns
router.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
router.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
router.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
- Use trace IDs for error tracking
Advanced Patterns
Response Builder
// 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
// Server-Sent Events
router.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
router.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
Full REST API implementation showcasing all response features: pagination, validation, file uploads, and streaming.
// 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) {
router.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
Do's
- 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
Don'ts
- 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
- Don't ignore proper error logging
Questions about Responses?
Join our community to discuss response patterns and API best practices.
