MasterController

Routing

Map URLs to controller actions — one at a time, or a full REST resource.

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#

app/routes.js
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 :param segments, 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#

OptionMeaningASP.NET Core
constraintGuard function run before the controller (see below).
authorizetrue · 'PolicyName' · { roles, claims, schemes }Authentication.[Authorize]
allowAnonymousSkip route- and controller-level authorization.[AllowAnonymous]
outputCache{ duration, varyByQuery, varyByHeader, varyByUser, tags, cacheAuthenticated } · 'PolicyName' · trueCaching.[OutputCache]
nameName for master.urlFor(name, params) / this.urlFor().WithName / LinkGenerator
apiVersion'2.0' or ['2.0', '3.0']API Versioning.[ApiVersion]
deprecatedAdvertise 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.

javascript
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:

generated 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#destroy

router.resources(name, { only, except, authorize, outputCache })only/except select actions, authorize applies to the generated routes, and outputCache applies to index and show.

javascript
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'] } });
Tip
For a JSON API you typically register the five data routes explicitly (index/show/create/update/destroy) — which is exactly what master generate scaffold does.

Reading route parameters#

Parameter casing is preserved, so :periodId is available as obj.params.periodId:

javascript
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.

javascript
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#

javascript
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:

constraint
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
  },
});
Note
Prefer { 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.