Master Guides

Generators

Write less boilerplate — generate it, wired up and ready.

Master’s generators are idempotent and wiring-aware: they create files and also register routes (in routes.js) and models (in AppContext.js) for you. Run them from anywhere inside a Master project (a directory with master.config.js).

terminal
$ master generate <type> <name> [extra...]
$ master g <type> <name> [extra...] # alias

scaffold — a full vertical slice#

The headline generator. One command produces a model, a validated REST controller, typed routes, and a Next.js page.

terminal
$ master g scaffold post title:string body:text

Creates and wires:

  • backend/app/models/Post.js — entity, registered in AppContext.js
  • backend/app/controllers/postsController.js — a RESTful JSON controller backed by the request-scoped context (this.db), with static schemas derived from your fields (model binding + validation → this.model, automatic 400 ValidationProblemDetails) and typed results (this.ok / created / noContent / notFound). A commented static authorize shows where to require a signed-in user for writes.
  • frontend/app/posts/page.tsx — a Server Component list page that fetches /posts
  • 5 routes appended to routes.js with a typed :id(int) parameter — /posts/abc is a 404, not a query:
MethodPathActionReplies
GET/postsindex200 { data: [...] }
GET/posts/:id(int)show200 / 404
POST/postscreate201 + Location / 400
PUT/posts/:id(int)update200 / 400 / 404
DELETE/posts/:id(int)destroy204 / 404
generated Post.js
export default class Post {
  id(db) { db.integer().primary().auto(); }
  title(db) { db.string(); }
  body(db) { db.text(); }
}
generated postsController.js
// PostsController — RESTful JSON API backed by MasterRecord.
// this.db is a request-scoped AppContext (ASP.NET/EF: scoped DbContext) — registered in server.js.
// Input is bound + validated from `static schemas` into this.model (invalid -> 400 ValidationProblemDetails).
export default class PostsController {
  static schemas = {
    create: { body: { title: { type: 'string', required: true }, body: { type: 'string', required: true } } },
    update: { body: { title: { type: 'string' }, body: { type: 'string' } } },
  };

  // Protect writes once auth is configured in server.js (ASP.NET [Authorize]):
  // static authorize = { create: true, update: true, destroy: true };

  constructor(requestObject) {
    this.requestObject = requestObject;
  }

  // GET /posts
  async index() {
    const data = await this.db.Post.toList();
    this.ok({ data });
  }

  // GET /posts/:id
  async show(obj) {
    const item = await this.db.Post.find(obj.params.id);
    if (!item) return this.notFound('Post not found');
    this.ok({ data: item });
  }

  // POST /posts
  async create() {
    const item = this.db.Post.new();
    Object.assign(item, this.model);
    await this.db.saveChanges();
    this.created(`/posts/${item.id}`, { data: item });
  }

  // PUT /posts/:id
  async update(obj) {
    const item = await this.db.Post.find(obj.params.id);
    if (!item) return this.notFound('Post not found');
    Object.assign(item, this.model);
    await this.db.saveChanges();
    this.ok({ data: item });
  }

  // DELETE /posts/:id
  async destroy(obj) {
    const item = await this.db.Post.find(obj.params.id);
    if (!item) return this.notFound('Post not found');
    this.db.Post.remove(item);
    await this.db.saveChanges();
    this.noContent();
  }
}

The validation rule for each field follows its column type (string/text string, integerinteger, booleanboolean, datetimedatetime, uuiduuid, …); every field is required on create (booleans excepted) and optional on update.

Then: master db new AddPosts && master db migrate. The new endpoints appear in /openapi.json and the Swagger UI at /docs automatically.

controller#

terminal
$ master g controller users index show profile

Creates backend/app/controllers/usersController.js with one action per name (default index) and a GET route for each (/users, /users/show, /users/profile). Actions reply with this.ok(...); the header comments show where static authorize and static schemas go.

generated usersController.js (one action shown)
// UsersController — MasterController v2 API controller (ESM).
// Each request constructs a fresh instance; actions receive the request object and
// reply with typed results: this.ok / created / noContent / notFound / problem / file.
// this.db = request-scoped AppContext, this.user = ClaimsPrincipal, this.logger, this.options.
export default class UsersController {
  // Authorization (ASP.NET [Authorize]) once auth is configured in server.js:
  // static authorize = { '*': true, index: false };
  // Model binding + validation (ASP.NET [ApiController]):
  // static schemas = { create: { body: { name: 'string!' } } };

  constructor(requestObject) {
    this.requestObject = requestObject;
  }

  // GET-by-default action. Reply with JSON for the Next.js frontend to consume.
  async index(obj) {
    this.ok({
      ok: true,
      controller: 'users',
      action: 'index',
      params: obj.params,
      user: this.user && this.user.isAuthenticated ? this.user.name : null,
    });
  }
}

model#

terminal
$ master g model Comment body:text author:string created_at:datetime

Creates backend/app/models/Comment.js with an auto-increment id and registers its dbset in AppContext.js. Field types map to native columns — see the field-type reference. Add relationships and modifiers by hand:

Comment.js (after adding a relationship)
export default class Comment {
  id(db) { db.integer().primary().auto(); }
  body(db) { db.text(); }
  author(db) { db.string(); }
  created_at(db) { db.datetime(); }
  post_id(db) { db.belongsTo('Post'); }
}

page#

terminal
$ master g page about

Creates frontend/app/about/page.tsx, a Next.js App Router Server Component that imports the api() helper.

socket / middleware / component#

terminal
$ master g socket chat message typing # app/sockets/chatSocket.js — io({ query: { socket: 'chat' } })
$ master g middleware auth # middleware/NN-auth.js — (ctx, next), auto-loaded alphabetically
$ master g component billing # components/billing/{config/routes.js, app/controllers/...}

A component is a mountable mini-app; register it in server.js after setupServer with await master.component('components', 'billing').

Field syntax#

name:type pairs
Aliases → builder: string/str, text, mediumtext, longtext, int/integer/number, bigint, float, decimal, bool/boolean, time, date, datetime, timestamp, json, uuid, binary/blob. Unknown types fall back to string.