Routing in Vania
In Vania, routes are the paths that take incoming requests and send them to the right controllers or handlers. All route files are in the routes
folder and are loaded automatically by the RouteServiceProvider
.
What is a Route file?
A Route file is a Dart class that extends the Route
base class and defines a set of routes in its register()
method. You can add a prefix
, middleware, and other settings to group and protect routes.
Note: If you create a new route file, you must import and register it in
lib/app/providers/route_service_provider.dart
. Without this, Vania will not load your routes.
import 'package:vania/route.dart';
class ApiRoute extends Route {
// Add a URL prefix to all routes in this file (e.g., api/v1)
@override
String prefix = 'api/v1';
@override
void register() {
super.register();
// Define your routes here
}
}
HTTP Methods
Each route responds to a specific HTTP method. Here are the most common ones:
-
GET
// Returns an HTML welcome message
Router.get('/hello', () => Response.html('<h1>Hello, World!</h1>')); -
POST
// Create a new user from JSON in the request body
Router.post('/users', (Request req) {
final data = req.all();
// save user...
return Response.json({'message': 'User created'});
}); -
PUT
// Update entire user data
Router.put('/users/{id}', (int id, Request req) {
final data = req.all();
// replace user with id
return Response.json({'message': 'User $id updated'});
}); -
PATCH
// Update user email only
Router.patch('/users/{id}', (int id, Request req) {
final changes = req.all();
// update email
return Response.json({'message': 'User $id email updated'});
}); -
DELETE
// Delete a user by id
Router.delete('/users/{id}', (int id) {
// delete user
return Response.json({'message': 'User $id deleted'});
}); -
OPTIONS
Router.options('/users', () => Response.json({'methods': ['GET', 'POST']}));
-
ANY: Respond to all methods (Sometimes you may need to register a route that responds to all HTTP requests using the any method)
Router.any('/status', () => Response.json({'status': 'OK'}));
-
WebSocket: Open a WebSocket connection.
Router.websocket('/ws', (WebSocketEvent event) { /* ... */ });
Route Parameters
Use {param}
in the URL to capture values as function arguments:
Router.get('/user/{id}', (int id) {
return Response.json({'userId': id});
});
You can add multiple:
Router.get(
'/posts/{post}/comments/{comment}',
(String postId, String commentId) {
return Response.json({
'post': postId,
'comment': commentId
});
}
);
Parameter Constraints
Ensure parameters match a type or format. If they don’t match, Vania returns a 404 Not Found error.
-
whereInt: only integers
Router.get('/order/{id}', (int id) => Response.json({'orderId': id}))
.whereInt('id'); -
whereString: only letters (a–z, A–Z)
Router.get('/product/{name}', (String name) => Response.json({'product': name}))
.whereString('name'); -
whereDouble: only decimal numbers
Router.get('/price/{value}', (double value) => Response.json({'price': value}))
.whereDouble('value'); -
whereBool: only
true
orfalse
Router.get('/feature/{enabled}', (bool enabled) => Response.json({'enabled': enabled}))
.whereBool('enabled');
Or use a custom regex with .where('param', r'<pattern>')
.
Custom Regex Constraints
You can define your own regex patterns for parameters. If the parameter does not match, Vania returns a 404 Not Found.
// Can accept both letters, numbers, and dashes
Router.get('/user/{slug}', (dynamic slug) {
return Response.html(slug);
}).where('slug', r'[a-z0-9-]+'); // Can accept String and int
// Only lowercase letters
Router.get('/user/{name}', (String name) {
return Response.html(name);
}).where('slug', r'[a-z]+'); // Name must be String
// Only digits
Router.get('/user/{id}', (int id) {
return Response.html('\$id');
}).where('slug', r'[0-9]+'); // Id must be number
Domain Constraints
Domain constraints allow you to restrict routes to specific hostnames. This is useful when:
- Multi-tenant apps: Each customer has a unique subdomain (e.g.
company1.app.com
,company2.app.com
). You can serve different data or themes based on{tenant}.app.com
. - Admin vs. Public areas: You might want admin routes only on
admin.example.com
and keep public routes onwww.example.com
. - Feature flags or staging: Route traffic for testing on
beta.example.com
without touching the main site. - Security and isolation: Limiting sensitive or internal APIs to a private hostname.
When a request’s Host
header does not match the domain pattern, Vania returns a 404 Not Found.
// Any subdomain before example.com
Router.get('/user/{slug}', (String slug) => Response.json({'slug': slug}))
.domain('{username}');
// Only admin.example.com
Router.get('/admin', () => Response.json({'area': 'admin'}))
.domain('admin');
Named Routes
Give a route a name for URL generation or redirects:
Router.get('/user/{slug}', (String slug) => Response.json({'slug': slug}))
.name('user.profile');
// Later generate URL by name
String url = Router.url('user.profile', {'slug': 'john'});
Route Modifiers
You can apply prefix and middleware directly on individual routes using chainable methods:
prefix(String path): Add a URL prefix to a single route.
// Available at /test/dashboard
Router.get('/dashboard', () => Response.json({'ok': true}))
.prefix('test');
middleware: Attach middleware to run before the handler.
// Only accessible if AuthMiddleware passes
Router.get('/settings', () => Response.json({'settings': []}))
.middleware([AuthMiddleware()]);
You can combine both on one route:
Router.post('/settings', (Request req) => Response.json({'saved': true}))
.prefix('test')
.middleware([AuthMiddleware(), Logging()]);
Route Groups
Route groups allow you to share route attributes—such as prefix and middleware—across many routes at once. This keeps your code clean and avoids repetition.
Router.group(() {
// Handles GET /api/v1/users
Router.get('/', () => Response.json({'users': []}));
// Handles GET /api/v1/users/details/1
Router.get('/details/{id}', (int id) => Response.json({'details': id}));
// Handles POST /api/v1/users/create
Router.post('/create', (Request req) {
// create user...
return Response.json({'status': 'created'});
});
}, prefix: 'api/v1/users', middleware: [Authenticate()]);
prefix
: Prepends a base path (api/v1/users
) to all routes in the group.middleware
: Runs shared logic (like authentication) before each route handler.
Use groups when you have multiple routes with the same middleware or URL segment.
Resource Routes
Quickly create CRUD routes for a resource:
Router.resource('posts', PostController());
This generates standard routes and expects your PostController
to have these methods:
HTTP Method | URL | Class(Controller) Method |
---|---|---|
GET | /posts | index() |
GET | /posts/{id} | show(int id) |
POST | /posts | store(Request req) |
GET | /posts/create | create() |
GET | /posts/{id}/edit | edit(int id) |
PUT | /posts/{id} | update(Request req, int id) |
PATCH | /posts/{id} | update(Request req, int id) |
DELETE | /posts/{id} | destroy(int id) |
Make sure your controller uses these method names so Vania can map routes correctly.
WebSocket Routes
class WebSocketRoute extends Route {
@override
void register() {
Router.websocket('/ws', (WebSocketEvent event) {
event.on('message', (WebSocketClient client, dynamic msg) {
print('Received: $msg');
});
});
}
}
Tips
- Always call
super.register()
insideregister()
. - Remove
prefix
if you don’t need a base path. - Use groups and middleware to organize and protect routes.