Routing
Routing is at the heart of any web framework. Arcade provides a clean, Express-like routing API that’s both powerful and easy to use.
Basic Routing
Section titled “Basic Routing”Routes in Arcade are defined using the global route
object:
route.<method>(path).handle(handler);
HTTP Methods
Section titled “HTTP Methods”Arcade supports all standard HTTP methods:
route.get('/users').handle((context) => 'GET users');route.post('/users').handle((context) => 'POST user');route.put('/users/:id').handle((context) => 'PUT user');route.delete('/users/:id').handle((context) => 'DELETE user');route.patch('/users/:id').handle((context) => 'PATCH user');route.head('/users').handle((context) => 'HEAD users');route.options('/users').handle((context) => 'OPTIONS users');
There’s also a special any
method that matches all HTTP methods:
route.any('/api/*').handle((context) => 'Matches any method');
Path Parameters
Section titled “Path Parameters”Capture dynamic segments in your URLs using the :param
syntax:
route.get('/users/:id').handle((context) { final userId = context.pathParameters['id']; return {'userId': userId};});
// Multiple parametersroute.get('/posts/:postId/comments/:commentId').handle((context) { final postId = context.pathParameters['postId']; final commentId = context.pathParameters['commentId']; return { 'postId': postId, 'commentId': commentId, };});
Wildcards
Section titled “Wildcards”Use wildcards to match multiple path segments:
// Matches /files/image.jpg, /files/docs/report.pdf, etc.route.get('/files/*').handle((context) { final path = context.path; return {'requestedFile': path};});
Query Parameters
Section titled “Query Parameters”Access query string parameters through the context:
// GET /search?q=arcade&limit=10route.get('/search').handle((context) { final query = context.queryParameters['q'] ?? ''; final limit = int.tryParse(context.queryParameters['limit'] ?? '20') ?? 20;
return { 'query': query, 'limit': limit, };});
Route Groups
Section titled “Route Groups”Organize related routes using groups:
route.group<RequestContext>('/api/v1', defineRoutes: (route) { // All routes in this group will be prefixed with /api/v1
route().get('/users').handle((context) => 'List users'); route().post('/users').handle((context) => 'Create user');
// Nested groups route().group<RequestContext>('/admin', defineRoutes: (route) { // This will be /api/v1/admin/dashboard route().get('/dashboard').handle((context) => 'Admin dashboard'); });});
Groups with Hooks
Section titled “Groups with Hooks”Apply hooks to all routes in a group:
route.group<RequestContext>( '/api', before: [ (context) { // This runs before all routes in the group print('API request: ${context.path}'); return context; }, ], after: [ (context, result) { // This runs after all routes in the group print('API response sent'); return (context, result); }, ], defineRoutes: (route) { route().get('/users').handle((context) => []); route().get('/posts').handle((context) => []); },);
Route Order
Section titled “Route Order”Routes are matched in the order they are defined. More specific routes should be defined before generic ones:
// Define specific routes firstroute.get('/users/me').handle((context) => 'Current user');route.get('/users/:id').handle((context) => 'User by ID');
// Generic wildcard routes lastroute.get('/users/*').handle((context) => 'Other user routes');
Not Found Handler
Section titled “Not Found Handler”Define a custom handler for 404 errors:
route.notFound((context) { context.statusCode = 404; return { 'error': 'Not Found', 'path': context.path, 'timestamp': DateTime.now().toIso8601String(), };});
Route Metadata
Section titled “Route Metadata”Attach metadata to routes for documentation or other purposes:
route.get( '/api/users', extra: { 'description': 'List all users', 'auth': true, 'roles': ['admin', 'user'], },).handle((context) { // Access metadata final metadata = context.route.metadata?.extra; return {'users': []};});
Route Builder Pattern
Section titled “Route Builder Pattern”For complex applications, organize routes in separate functions:
void defineUserRoutes() { route.group<RequestContext>('/users', defineRoutes: (route) { route().get('/').handle(listUsers); route().get('/:id').handle(getUser); route().post('/').handle(createUser); route().put('/:id').handle(updateUser); route().delete('/:id').handle(deleteUser); });}
void defineAuthRoutes() { route.post('/login').handle(login); route.post('/logout').handle(logout); route.post('/register').handle(register);}
// In your main functionawait runServer( port: 3000, init: () { defineUserRoutes(); defineAuthRoutes(); },);
Dynamic Route Registration
Section titled “Dynamic Route Registration”Routes can be registered dynamically based on configuration:
final features = ['users', 'posts', 'comments'];
for (final feature in features) { route.get('/$feature').handle((context) => 'List $feature'); route.post('/$feature').handle((context) => 'Create $feature');}
Route Validation
Section titled “Route Validation”Arcade validates routes at startup:
- Duplicate route definitions are allowed (last one wins)
- Routes without handlers will cause an error
- Invalid path patterns will be caught early
Generic Type Annotations
Section titled “Generic Type Annotations”When using route groups, it’s recommended to specify the generic type parameter for better type inference:
route.group<RequestContext>('/api/v1', defineRoutes: (route) { // The route parameter here will have proper type inference route().get('/users').handle((context) => 'List users');});
Why use the generic type?
- Improves IDE autocomplete and type checking
- Enables better type inference in the
defineRoutes
function - Makes the code more explicit and self-documenting
- Helps prevent type-related runtime errors
Without generic type:
route.group('/api', defineRoutes: (route) { // route parameter may have limited type inference route().get('/users').handle((context) => 'Users');});
With generic type:
route.group<RequestContext>('/api', defineRoutes: (route) { // route parameter has full type inference route().get('/users').handle((context) => 'Users');});
This pattern is especially important when using custom context types or when working with hooks that transform the context type.
Best Practices
Section titled “Best Practices”- Organize routes logically - Use groups and separate functions
- Be consistent with naming - Use RESTful conventions
- Define specific routes first - More generic patterns last
- Use meaningful path parameters -
:userId
instead of:id
- Handle errors appropriately - Define not found and error handlers
- Use generic type annotations - Specify
<RequestContext>
for better type inference
Next Steps
Section titled “Next Steps”- Learn about Request Context to handle requests
- Explore Hooks to add pre/post processing
- Understand Error Handling for robust applications