Routing System
Build powerful APIs with Khadem's intuitive routing system. Clean, modular HTTP routing with parameters, middleware, and route groups.
Express-like Routing
Familiar routing patterns with powerful features like parameter extraction, middleware chains, and nested groups for building scalable REST APIs.
Basic Routes
Define routes using HTTP methods with clean, expressive syntax. Each route accepts a path and a handler function.
// Basic HTTP method routes
router.get('/users', (req, res) async {
final users = await User.all();
res.sendJson({'users': users});
});
router.post('/users', (req, res) async {
final data = await req.body;
final user = await User.create(data);
res.statusCode(201).sendJson({'user': user});
});
router.put('/users/:id', (req, res) async {
final userId = req.param('id');
final data = await req.body;
final user = await User.find(userId).update(data);
res.sendJson({'user': user});
});
router.delete('/users/:id', (req, res) async {
final userId = req.param('id');
await User.find(userId).delete();
res.statusCode(204).empty();
});HTTP Methods
GETRetrieve resourcesPOSTCreate new resourcesPUTUpdate resourcesPATCHPartial updatesDELETERemove resourcesHEADHeaders onlyOPTIONSCORS preflightHandler Signature
Future Function(Request, Response) All handlers are async functions that receive Request and Response objects.
Route Parameters
Extract dynamic values from URLs using the :param syntax. Perfect for building RESTful APIs with resource identifiers.
// Route parameters with :param syntax
router.get('/users/:id', (req, res) async {
final userId = req.param('id');
final user = await User.find(userId);
if (user == null) {
return res.statusCode(404).sendJson({
'error': 'User not found'
});
}
res.sendJson({'user': user});
});
// Multiple parameters
router.get('/posts/:category/:slug', (req, res) async {
final category = req.param('category');
final slug = req.param('slug');
final post = await Post.where('category', category)
.where('slug', slug)
.first();
res.sendJson({'post': post});
});
// Optional query parameters
router.get('/search', (req, res) async {
final query = req.query['q'] ?? '';
final page = int.tryParse(req.query['page'] ?? '1') ?? 1;
final limit = int.tryParse(req.query['limit'] ?? '10') ?? 10;
final results = await Search.query(query, page: page, limit: limit);
res.sendJson({'results': results});
});Parameter Features
:paramsyntax for named parameters- Automatic parameter extraction
- Type-safe parameter access
- Regex-based matching support
Route Groups
Organize related routes with shared prefixes and middleware. Group routes for better code organization and consistent configuration across multiple endpoints.
// Route groups with shared prefix and middleware
router.group(
prefix: '/api/v1',
middleware: [AuthMiddleware(), ApiMiddleware()],
routes: (r) {
r.get('/users', UserController.index);
r.post('/users', UserController.store);
r.get('/users/:id', UserController.show);
r.put('/users/:id', UserController.update);
r.delete('/users/:id', UserController.destroy);
// Nested groups
r.group(
prefix: '/admin',
middleware: [AdminMiddleware()],
routes: (adminRouter) {
adminRouter.get('/stats', AdminController.stats);
adminRouter.post('/users/:id/ban', AdminController.banUser);
}
);
}
);Group Benefits
- Shared URL prefixes - Avoid repeating paths
- Common middleware - Apply to all group routes
- Better organization - Group by feature/version
- Nested groups - Create hierarchies
Generated Routes
Generated routes:
GET /api/v1/users
POST /api/v1/users
GET /api/v1/users/:id
PUT /api/v1/users/:id
DELETE /api/v1/users/:id
GET /api/v1/admin/stats
POST /api/v1/admin/users/:id/banAll routes inherit the group prefix and middleware
Middleware Integration
Apply middleware at global, group, or route level for authentication, logging, validation, and more.
// Global middleware (apply these on the Server instance before injecting routes)
final server = Server();
server.applyMiddlewares([
LoggingMiddleware(),
CorsMiddleware(),
RateLimitMiddleware(),
]);
server.injectRoutes(registerRoutes);
// Group middleware (inside a registerRoutes(ServerRouter router) function)
router.group(
prefix: '/admin',
middleware: [AuthMiddleware(), AdminMiddleware()],
routes: (r) {
r.get('/dashboard', AdminController.dashboard);
}
);
// Route-specific middleware (inside router)
r.get('/public', PublicController.index);
r.get('/protected', ProtectedController.index,
middleware: [AuthMiddleware()]);
r.get('/admin-only', AdminController.index,
middleware: [AuthMiddleware(), AdminMiddleware()]);Middleware Levels
Applied to all routes in your application
Shared across all routes in a group
Applied to specific individual routes
Static File Serving
Serve static assets like CSS, JavaScript, images, and downloads with automatic MIME type detection.
// Serve static files from public directory
server.serveStatic('public');
// Custom static directory
server.serveStatic('assets');
// Now these URLs work automatically:
// GET /css/style.css → public/css/style.css
// GET /images/logo.png → public/images/logo.png
// GET /js/app.js → public/js/app.jsStatic Serving Features
- Automatic file type detection
- Directory browsing disabled (security)
- Configurable root directory
- Efficient caching headers
Advanced Patterns
Query Parameters
router.get('/search', (req, res) async {
// Query parameters
final query = req.query['q'];
final page = req.query['page'];
final sort = req.query['sort'];
// Form data (for POST/PUT)
final formData = await req.body;
final name = formData['name'];
final email = formData['email'];
res.sendJson({
'query': query,
'page': page,
'sort': sort,
'form': {'name': name, 'email': email}
});
});Access URL query strings and form data easily
Response Types
// JSON response
res.sendJson({'message': 'Hello World'});
// HTML response
await res.view('welcome');
// File download
await res.download('files/report.pdf');
// Stream response
await res.stream<String>(
Stream.periodic(Duration(seconds: 1), (i) => 'Line $i
').take(10)
);
// Custom status
res.statusCode(201).sendJson({'created': true});
// Empty response
res.statusCode(204).empty();JSON, HTML, files, and streaming responses
Complete API Example
Here's a full example showing how to structure a production-ready REST API with authentication, versioning, and nested resources.
import 'package:khadem/khadem_dart.dart';
// Recommended startup (apply global middleware on Server, then inject routes)
final server = Server();
server.applyMiddlewares([
LoggingMiddleware(),
CorsMiddleware(),
RateLimitMiddleware(),
]);
server.injectRoutes(registerRoutes);
server.serveStatic('public');
await server.start(port: 8080);
// Route registration receives a ServerRouter
void registerRoutes(ServerRouter router) {
// Public routes
router.group(
prefix: '/api/v1',
routes: (r) {
r.get('/health', (req, res) async {
res.sendJson({
'status': 'ok',
'timestamp': DateTime.now().toIso8601String()
});
});
}
);
// User routes (authenticated)
router.group(
prefix: '/api/v1/users',
middleware: [AuthMiddleware()],
routes: (r) {
r.get('', UserController.index);
r.post('', UserController.store);
r.get('/:id', UserController.show);
r.put('/:id', UserController.update);
r.delete('/:id', UserController.destroy);
// User relationships
r.get('/:id/posts', UserController.posts);
r.get('/:id/followers', UserController.followers);
}
);
// Post routes
router.group(
prefix: '/api/v1/posts',
middleware: [AuthMiddleware()],
routes: (r) {
r.get('', PostController.index);
r.post('', PostController.store);
r.get('/:id', PostController.show);
r.put('/:id', PostController.update);
r.delete('/:id', PostController.destroy);
// Comments
r.get('/:id/comments', PostController.comments);
r.post('/:id/comments', PostController.addComment);
}
);
// Admin routes
router.group(
prefix: '/api/v1/admin',
middleware: [AuthMiddleware(), AdminMiddleware()],
routes: (r) {
r.get('/dashboard', AdminController.dashboard);
r.get('/stats', AdminController.stats);
r.get('/users', AdminController.users);
r.post('/users/:id/ban', AdminController.banUser);
}
);
}Best Practices
Do's
- Use route groups for logical organization by feature or API version
- Apply middleware at the appropriate level (global, group, or route)
- Use descriptive parameter names that reflect the resource
- Keep route handlers focused - delegate to controllers/services
- Always validate and sanitize input parameters
- Return appropriate HTTP status codes (200, 201, 404, etc.)
Don'ts
- Don't put business logic directly in route handlers
- Don't create deeply nested route groups (max 2-3 levels)
- Don't use generic parameter names like
:ideverywhere - Don't skip input validation - always validate user input
- Don't forget error handling - use try-catch blocks
- Don't expose sensitive information in error responses
Need Help with Routing?
Join our community to get help with routing patterns and API design.
