Minimal APIs & Route Groups
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
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);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
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#
// 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.