Skip to content

Request Context

The RequestContext is the central object that provides access to all request and response data in Arcade. Every route handler receives a context object as its parameter.

route.get('/example').handle((RequestContext context) {
// Access request data
final path = context.path;
final method = context.method;
// Set response data
context.statusCode = 200;
return {'path': path, 'method': method.name};
});
route.get('/api/users').handle((context) {
print(context.path); // "/api/users"
print(context.method); // HttpMethod.get
return 'OK';
});

Access request headers:

route.get('/api/data').handle((context) {
// Get a specific header
final authHeader = context.requestHeaders.value('authorization');
final contentType = context.requestHeaders.contentType;
// Check if header exists
if (authHeader == null) {
throw UnauthorizedException();
}
return {'authorized': true};
});

Extract dynamic segments from the URL:

route.get('/users/:userId/posts/:postId').handle((context) {
final userId = context.pathParameters['userId'];
final postId = context.pathParameters['postId'];
return {
'userId': userId,
'postId': postId,
};
});

Access URL query string parameters:

// GET /search?q=arcade&page=2&limit=20
route.get('/search').handle((context) {
final query = context.queryParameters['q'] ?? '';
final page = int.tryParse(context.queryParameters['page'] ?? '1') ?? 1;
final limit = int.tryParse(context.queryParameters['limit'] ?? '10') ?? 10;
return {
'query': query,
'page': page,
'limit': limit,
};
});

Get the raw request body as bytes:

route.post('/upload').handle((context) async {
final rawBody = await context.rawBody;
// rawBody is List<Uint8List>
return {'bytes': rawBody.length};
});

Get the body as a string:

route.post('/text').handle((context) async {
final text = await context.body();
print('Received: $text');
return 'Text received';
});

Parse JSON bodies with error handling:

route.post('/api/users').handle((context) async {
final result = await context.jsonMap();
switch (result) {
case BodyParseSuccess(:final value):
// Successfully parsed JSON
final name = value['name'] as String?;
final email = value['email'] as String?;
if (name == null || email == null) {
context.statusCode = 400;
return {'error': 'Name and email are required'};
}
return {'id': 1, 'name': name, 'email': email};
case BodyParseFailure(:final error):
// Failed to parse JSON
context.statusCode = 400;
return {'error': 'Invalid JSON: ${error.toString()}'};
}
});

Parse JSON arrays:

route.post('/api/bulk').handle((context) async {
final result = await context.jsonList();
if (result case BodyParseSuccess(:final value)) {
return {
'count': value.length,
'items': value,
};
}
context.statusCode = 400;
return {'error': 'Expected JSON array'};
});

Convert JSON to typed objects:

class CreateUserDto {
final String name;
final String email;
CreateUserDto.fromJson(Map<String, dynamic> json)
: name = json['name'],
email = json['email'];
}
route.post('/api/users').handle((context) async {
final result = await context.parseJsonAs(CreateUserDto.fromJson);
if (result case BodyParseSuccess(:final value)) {
// value is CreateUserDto
return {'created': value.name};
}
context.statusCode = 400;
return {'error': 'Invalid user data'};
});

Handle multipart form data with file uploads:

route.post('/upload').handle((context) async {
final result = await context.formData();
if (result case BodyParseSuccess(:final value)) {
// Access form fields
final title = value.data['title'];
// Access uploaded files
for (final file in value.files) {
print('Uploaded: ${file.path}');
// Process file...
}
return {'uploaded': value.files.length};
}
context.statusCode = 400;
return {'error': 'Invalid form data'};
});

Set the HTTP status code:

route.get('/api/secret').handle((context) {
if (!isAuthorized(context)) {
context.statusCode = 401;
return {'error': 'Unauthorized'};
}
context.statusCode = 200; // Default
return {'secret': 'data'};
});

Set response headers:

route.get('/api/data').handle((context) {
// Set content type
context.responseHeaders.contentType = ContentType.json;
// Set custom headers
context.responseHeaders.add('X-API-Version', '1.0');
context.responseHeaders.add('Cache-Control', 'no-cache');
return {'data': 'value'};
});

Access information about the matched route:

route.get('/api/info', extra: {'version': '1.0'}).handle((context) {
final route = context.route;
return {
'path': route.path,
'method': route.method?.name,
'metadata': route.metadata?.extra,
};
});

For advanced use cases, access the underlying Dart HttpRequest:

route.get('/advanced').handle((context) {
final rawRequest = context.rawRequest;
// Access low-level request properties
final remoteAddress = rawRequest.connectionInfo?.remoteAddress;
final remotePort = rawRequest.connectionInfo?.remotePort;
return {
'remote': '${remoteAddress?.address}:$remotePort',
};
});

Create custom context classes for type-safe access to additional data:

class AuthenticatedContext extends RequestContext {
final User user;
AuthenticatedContext({
required super.request,
required super.route,
required this.user,
});
}
// Use in before hook
route.get('/profile')
.before<AuthenticatedContext>((context) async {
final token = context.requestHeaders.value('authorization');
final user = await validateToken(token);
return AuthenticatedContext(
request: context.rawRequest,
route: context.route,
user: user,
);
})
.handle((AuthenticatedContext context) {
return {
'user': context.user.toJson(),
};
});
  1. Always handle parse failures - JSON/form parsing can fail
  2. Set appropriate status codes - Don’t rely on defaults
  3. Use type-safe parsing - Convert to DTOs when possible
  4. Validate input early - Check required fields
  5. Set content types - Especially for non-JSON responses