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.

dart
// 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 responses
send()Plain text
html()HTML content
file()File downloads
stream()Data streaming
bytes()Binary data

Status Code Methods

ok()200 OK
created()201 Created
noContent()204 No Content
badRequest()400 Bad Request
unauthorized()401 Unauthorized
notFound()404 Not Found

JSON Responses

Send JSON data with automatic content-type headers and proper encoding. Perfect for REST APIs.

dart
// 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.

dart
// 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

dart
// 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.

dart
// 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.

dart
// 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

dart
// 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

dart
// 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.

dart
// 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

Static Assets

Long-term caching (1 year)

API Data

Short-term caching (5-60 min)

Dynamic Content

No caching

Session & Flash Messages

Store data across requests and display one-time flash messages for user feedback.

dart
// 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.

dart
// 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

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
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.

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) {
  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.

On this page