Routing
Routes live in app/routes.js, loaded once at startup by startMVC(). Each route maps a URL + HTTP method to a controller#action, optionally with typed parameter constraints and endpoint options such as authorize or outputCache.
Defining routes#
import master from 'mastercontroller';
const router = master.router.start();
// Basic route
router.route('/health', 'health#index', 'get', { allowAnonymous: true });
// Route parameters (casing preserved!)
router.route('/period/:periodId/items/:itemId', 'period#show', 'get');
// Typed constraint — only matches integers, obj.params.id is a Number
router.route('/items/:id(int)', 'items#show', 'get', { name: 'item' });
// RESTful resource — generates the full set of routes
router.resources('posts', { authorize: true, except: ['new', 'edit'] });router.route(path, toPath, method, options?)#
path— URL, with:paramsegments, optionally constrained::id(int).toPath—'controller#action'.method—'get','post','put','patch','delete'.options— an options object (below), or — for backwards compatibility — a bare constraint function.
Route options#
| Option | Meaning | ASP.NET Core |
|---|---|---|
constraint | Guard function run before the controller (see below). | — |
authorize | true · 'PolicyName' · { roles, claims, schemes } — Authentication. | [Authorize] |
allowAnonymous | Skip route- and controller-level authorization. | [AllowAnonymous] |
outputCache | { duration, varyByQuery, varyByHeader, varyByUser, tags, cacheAuthenticated } · 'PolicyName' · true — Caching. | [OutputCache] |
name | Name for master.urlFor(name, params) / this.urlFor(). | WithName / LinkGenerator |
apiVersion | '2.0' or ['2.0', '3.0'] — API Versioning. | [ApiVersion] |
deprecated | Advertise the version in api-deprecated-versions. | [Obsolete] |
Typed constraints#
Append a constraint to a parameter: int, number, bool, uuid, alpha, alphanum, slug, length(n), minlength(n), maxlength(n), min(n), max(n), regex:…. A non-matching request lets the next route try; numeric constraints coerce the parameter. Unknown names fail at registration.
router.route('/items/:id(int)', 'items#show', 'get'); // {id:int}
router.route('/items/:slug(slug)', 'items#bySlug', 'get');
router.route('/codes/:code(regex:^[A-Z]{3}$)', 'items#code', 'get');
router.route('/users/:id(uuid)', 'users#show', 'get');RESTful resources#
router.resources('posts') generates the conventional routes:
GET /posts -> posts#index
GET /posts/new -> posts#new
POST /posts -> posts#create
GET /posts/:id -> posts#show
GET /posts/:id/edit -> posts#edit
PUT /posts/:id -> posts#update
DELETE /posts/:id -> posts#destroyrouter.resources(name, { only, except, authorize, outputCache }) — only/except select actions, authorize applies to the generated routes, and outputCache applies to index and show.
router.resources('posts', { only: ['index', 'show', 'create', 'update', 'destroy'] });
router.resources('posts', { authorize: 'Senior', except: ['index', 'show'] });
router.resources('products', { outputCache: { duration: 30, tags: ['products'] } });master generate scaffold does.Reading route parameters#
Parameter casing is preserved, so :periodId is available as obj.params.periodId:
show(obj) {
const periodId = obj.params.periodId; // not "periodid"
this.returnJson({ periodId });
}Route groups#
router.group(prefix, options, (g) => …) prefixes every route inside and hands down authorize, allowAnonymous, outputCache, and apiVersion as defaults (MapGroup). Groups nest; routes can override.
router.group('/api/v1', { authorize: true }, (g) => {
g.route('/me', 'account#me', 'get');
g.route('/status', 'status#index', 'get', { allowAnonymous: true });
g.resources('posts', { except: ['destroy'] }); // /api/v1/posts…
g.group('/admin', { authorize: { roles: ['admin'] } }, (gg) => gg.route('/stats', 'admin#stats', 'get', { name: 'adminStats' }));
});Named routes & urlFor#
router.route('/items/:id(int)', 'items#show', 'get', { name: 'item' });
master.urlFor('item', { id: 5 }); // '/items/5'
master.urlFor('item', { id: 5 }, { query: { tab: 'info' } }); // '/items/5?tab=info'
// in an action: this.created(this.urlFor('item', { id: item.id }), item);More on groups, names, and controller-less handlers in Minimal APIs & Route Groups.
Constraint functions#
A guard that runs before the controller — still supported alongside the declarative options:
router.route('/admin', 'admin#index', 'get', {
constraint(requestObject) {
if (!requestObject.user.isInRole('admin')) {
requestObject.response.statusCode = 403;
requestObject.response.end('Forbidden');
return;
}
this.next(); // continue to the controller
},
});{ authorize } for authentication and roles — it answers proper 401/403 ProblemDetails and shows up in OpenAPI. Use a constraint function for custom per-route logic that is not about identity.