MasterController

Minimal APIs & Route Groups

Handlers without controllers, groups with shared options, and URLs generated from route names.

Small endpoints do not need a controller file. master.map.get() and friends register a handler that runs exactly like a controller action (ASP.NET Core MapGet); router.group() is MapGroup; { name } plus urlFor() is WithName + LinkGenerator.

Minimal APIs#

master.map.get / post / put / patch / delete / any(path, handler, options). The handler receives the request object as its first argument; this carries the full controller surface — ok/created/noContent/problem/validationProblem/file, authorize/signIn/signOut/issueToken, user, model/modelState (when schemas is set), logger, options/configuration, urlFor, and every DI service. A returned value is sent as JSON.

server.js
// server.js
import master from 'mastercontroller';

const server = master.setupServer('http');

master.map.get('/health', () => ({ ok: true }), { allowAnonymous: true, name: 'health' });

master.map.post('/items', async function (obj) {
  const item = await this.db.Item.add(this.model);          // this.model validated from `schemas`
  await this.db.saveChanges();
  this.created(this.urlFor('item', { id: item.id }), item);
}, {
  authorize: { roles: ['admin'] },
  schemas: { body: { name: 'string!', qty: { type: 'integer', min: 1 } } },
  openapi: { summary: 'Create item' },
});

master.map.get('/items/:id(int)', function (obj) {
  return this.db.Item.find(obj.params.id);                  // a returned value is sent as JSON
}, { name: 'item', outputCache: { duration: 30 } });

await master.start(server);
server.listen(3001);
Note
Use a regular function (not an arrow) when you need this. Minimal routes can be registered before or after startMVC(), and they appear in /openapi.json under the minimal tag.

Options#

The third argument accepts the same endpoint metadata as a route: { authorize, allowAnonymous, outputCache, schemas, openapi, name, constraint } — plus apiVersion / deprecated when API versioning is on. Think RequireAuthorization(), AllowAnonymous(), CacheOutput(), WithName().

Route groups#

router.group(prefix, options, (g) => …) prefixes every route registered inside it and hands down authorize, allowAnonymous, outputCache and apiVersion as defaults. A route can override any of them; groups nest.

app/routes.js
// app/routes.js
const router = master.router.start();

router.group('/api/v1', { authorize: true, outputCache: { duration: 10 } }, (g) => {
  g.route('/me', 'account#me', 'get');                                  // inherits authorize + outputCache
  g.route('/status', 'status#index', 'get', { allowAnonymous: true });  // override per route
  g.resources('posts', { except: ['destroy'] });                        // /api/v1/posts…
  g.group('/admin', { authorize: { roles: ['admin'] } }, (gg) => {
    gg.route('/stats', 'admin#stats', 'get', { name: 'adminStats' });
  });
});

master.urlFor('adminStats');   // '/api/v1/admin/stats'

Named routes & urlFor#

javascript
// name a route…
router.route('/items/:id(int)', 'items#show', 'get', { name: 'item' });
master.map.get('/search', function () { /* … */ }, { name: 'search' });

// …and generate URLs from it (params are encoded, constraints respected, unknown names throw)
master.urlFor('item', { id: 5 });                               // '/items/5'
master.urlFor('item', { id: 5 }, { query: { tab: 'info' } });  // '/items/5?tab=info'
master.urlFor('search', {}, { query: { q: 'a b' }, absolute: true });

// inside a controller or minimal handler
this.created(this.urlFor('item', { id: item.id }), item);

master.urlFor(name, params, { query, absolute }) is available everywhere; this.urlFor(...) inside controllers and handlers. Errors are loud: a missing route name or a missing parameter throws rather than producing a broken link.