Context
The Context object is the heart of Aim. It provides access to the request, response, and per-request context variables for each HTTP request.
Overview
Every route handler and middleware receives a Context object:
app.get('/hello', (c) async {
// c is the Context object
return c.text('Hello!');
});Request (c.req)
Access request information through c.req:
Method and Path
app.notFound((c) async {
final method = c.req.method; // GET, POST, etc.
final path = c.req.path; // /users/123
return c.json({
'method': method,
'path': path,
});
});Headers
app.get('/headers', (c) async {
final headers = c.req.headers; // Map<String, String>
final userAgent = c.req.headers['user-agent'];
final auth = c.req.headers['authorization'];
return c.json({
'userAgent': userAgent,
'auth': auth,
});
});Query Parameters
app.get('/search', (c) async {
final queryParameters = c.req.queryParameters; // Map<String, String>
final q = c.req.queryParameters['q'];
final page = c.req.queryParameters['page'] ?? '1';
return c.json({
'query': q,
'page': int.parse(page),
});
});queryParameters holds one string per key; if a key repeats in the query string, only the last value is kept (this comes from Uri.queryParameters).
Example: GET /search?q=dart&page=2
Request Body
JSON Body
app.post('/users', (c) async {
final body = await c.req.json(); // Map<String, dynamic>
final name = body['name'];
final email = body['email'];
return c.json({
'created': {
'name': name,
'email': email,
},
}, statusCode: 201);
});Raw Body
import 'dart:convert';
import 'dart:typed_data';
app.post('/webhook', (c) async {
final chunks = await c.req.read().toList();
final raw = Uint8List.fromList(chunks.expand((chunk) => chunk).toList());
final text = utf8.decode(raw);
return c.text('Received ${raw.length} bytes');
});c.req.raw is a field, not a method — it's the platform-specific request object (an HttpRequest on aim_server), not the body bytes. To read the body, use c.req.read() (a Stream<List<int>>, inherited from the message base) as shown above, or c.req.json() / c.req.text() for the common cases.
Form Data
Use aim_server_form's formData() extension method on Request — no middleware to register:
import 'package:aim_server/aim_server.dart';
import 'package:aim_server_form/aim_server_form.dart';
app.post('/login', (c) async {
final form = await c.req.formData();
final username = form['username'];
final password = form['password'];
return c.json({'username': username});
});Multipart/File Upload
Use aim_server_multipart's multipart() extension method on Request — no middleware to register:
import 'package:aim_server/aim_server.dart';
import 'package:aim_server_multipart/aim_server_multipart.dart';
import 'package:aim_server_multipart/aim_server_multipart_io.dart';
app.post('/upload', (c) async {
final form = await c.req.multipart();
final file = form.file('avatar');
if (file != null) {
await file.saveTo('uploads/${file.filename}');
}
return c.json({'uploaded': file?.filename});
});Path Parameters
Extract parameters from the URL path using c.param():
// Single parameter
app.get('/users/:id', (c) async {
final id = c.param('id');
return c.json({'userId': id});
});
// Multiple parameters
app.get('/posts/:postId/comments/:commentId', (c) async {
final postId = c.param('postId');
final commentId = c.param('commentId');
return c.json({
'postId': postId,
'commentId': commentId,
});
});Response Methods
JSON Response
app.get('/api/users', (c) async {
return c.json({
'users': [
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': 'Bob'},
],
});
});
// With status code
app.post('/users', (c) async {
return c.json({'created': true}, statusCode: 201);
});Text Response
app.get('/hello', (c) async {
return c.text('Hello, World!');
});
// With status code
app.get('/not-found', (c) async {
return c.text('Not Found', statusCode: 404);
});HTML Response
app.get('/page', (c) async {
return c.html('''
<!DOCTYPE html>
<html>
<head><title>My Page</title></head>
<body><h1>Hello!</h1></body>
</html>
''');
});File Response
import 'dart:io';
app.get('/download', (c) async {
final file = File('assets/document.pdf');
return c.stream(
file.openRead(),
headers: {
'content-type': 'application/pdf',
'content-disposition': 'attachment; filename="document.pdf"',
},
);
});There is no c.bytes() method; use c.stream() (below) for binary content, whether it comes from a file or already-loaded bytes.
Redirect
app.get('/old-path', (c) async {
return c.redirect('/new-path');
});
// With status code (positional, not named)
app.get('/temp-redirect', (c) async {
return c.redirect('/new-path', 302);
});Stream Response
app.get('/stream', (c) async {
final stream = Stream.periodic(
Duration(seconds: 1),
(count) => 'Event $count\n',
).take(10);
return c.stream(stream);
});For Server-Sent Events (SSE), use aim_server_sse's sse() extension method on Context — no middleware to register:
import 'package:aim_server/aim_server.dart';
import 'package:aim_server_sse/aim_server_sse.dart';
app.get('/events', (c) async {
return c.sse((stream) async {
for (var i = 0; i < 10; i++) {
await Future.delayed(Duration(seconds: 1));
stream.send('Event $i');
}
});
});Response Headers
Set headers before sending the response:
app.get('/api', (c) async {
c.header('X-API-Version', '1.0');
c.header('X-Request-ID', generateId());
c.header('Cache-Control', 'no-cache');
return c.json({'data': 'value'});
});Variables
Use custom Variables classes for type-safe context variables:
class AppVariables extends Variables {
String? userId;
String? requestId;
DateTime? requestTime;
}
final app = Aim<AppVariables>(
variablesFactory: () => AppVariables(),
);
// Set in middleware
app.use((c, next) async {
c.variables.requestId = generateId();
c.variables.requestTime = DateTime.now();
return next();
});
// Use in handlers
app.get('/info', (c) async {
return c.json({
'requestId': c.variables.requestId,
'time': c.variables.requestTime?.toIso8601String(),
});
});Variables vs. runtime env
Variables are per-request values your middleware and handlers share. Runtime bindings such as Cloudflare Workers' env are a different concept and are exposed by the runtime adapter (for example c.env in aim_edge).
Middleware-Specific Variables
Many middleware packages extend the base Variables:
import 'package:aim_server_jwt/aim_server_jwt.dart';
final app = Aim<JwtVariables>(
variablesFactory: () => JwtVariables.create(
JwtOptions(
algorithm: HS256(
secretKey: SecretKey(secret: 'your-secret-key-at-least-32-chars'),
),
),
),
);
app.use(jwt());
app.get('/protected', (c) async {
final payload = c.variables.jwtPayload; // From JWT middleware
return c.json({'userId': payload['sub']});
});Context Lifecycle
- Request arrives → Aim creates a new
Context - Middleware chain → Each middleware can modify the context
- Route handler → Handler uses context to generate response
- Response sent → Context is discarded
app.use((c, next) async {
print('1. Middleware - before');
c.variables.startTime = DateTime.now();
await next();
print('4. Middleware - after');
});
app.get('/', (c) async {
print('2. Handler - start');
final response = c.json({'message': 'Hello'});
print('3. Handler - end');
return response;
});
// Output:
// 1. Middleware - before
// 2. Handler - start
// 3. Handler - end
// 4. Middleware - afterBest Practices
Type-Safe Variables
// ✅ Good - Type-safe
class MyVariables extends Variables {
String? userId;
}
final app = Aim<MyVariables>(
variablesFactory: () => MyVariables(),
);
// ❌ Bad - No type safety
final app = Aim();
// No compile-time checkingEarly Returns
// ✅ Good - Clear and concise
app.get('/users/:id', (c) async {
final id = c.param('id'); // non-nullable: the route wouldn't match without it
final user = await findUser(id);
if (user == null) {
return c.json({'error': 'Not found'}, statusCode: 404);
}
return c.json(user);
});Header Setting
// ✅ Good - Set headers before response
app.get('/api', (c) async {
c.header('X-Version', '1.0');
return c.json({'data': 'value'});
});
// ❌ Bad - Headers after response (won't work)
app.get('/api', (c) async {
final response = c.json({'data': 'value'});
c.header('X-Version', '1.0'); // Too late!
return response;
});Complete Example
import 'dart:io';
import 'package:aim_server/aim_server.dart';
class AppVariables extends Variables {
String? userId;
String? requestId;
DateTime? startTime;
}
void main() async {
final app = Aim<AppVariables>(
variablesFactory: () => AppVariables(),
);
// Middleware to track request
app.use((c, next) async {
c.variables.requestId = generateId();
c.variables.startTime = DateTime.now();
c.header('X-Request-ID', c.variables.requestId!);
await next();
final elapsed = DateTime.now().difference(c.variables.startTime!);
print('Request took ${elapsed.inMilliseconds}ms');
});
// Routes
app.get('/', (c) async {
return c.json({
'message': 'Welcome',
'requestId': c.variables.requestId,
});
});
app.get('/users/:id', (c) async {
final id = c.param('id');
return c.json({
'userId': id,
'requestId': c.variables.requestId,
});
});
app.post('/users', (c) async {
final body = await c.req.json();
return c.json({
'created': body,
'requestId': c.variables.requestId,
}, statusCode: 201);
});
await app.serve(host: InternetAddress.anyIPv4, port: 8080);
print('Server running on http://localhost:8080');
}
String generateId() {
return DateTime.now().millisecondsSinceEpoch.toString();
}Next Steps
- Learn about Middleware to process requests
- Explore Middleware Packages for common functionality
- Check out specific middleware like JWT or CORS
