Skip to main content

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 or false

    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 on www.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 MethodURLClass(Controller) Method
GET/postsindex()
GET/posts/{id}show(int id)
POST/postsstore(Request req)
GET/posts/createcreate()
GET/posts/{id}/editedit(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() inside register().
  • Remove prefix if you don’t need a base path.
  • Use groups and middleware to organize and protect routes.