Master Guides

Project Structure

One repo, two services, a clear place for everything.

master new generates a decoupled monorepo managed with npm workspaces: a MasterController API in backend/ and a Next.js app in frontend/. The backend comes production-shaped — layered configuration, a per-request database context, health endpoints, OpenAPI docs and an integration test — so there is nothing to bolt on later.

The tree#

my-app/
my-app/
├── package.json                 # workspaces + dev/build/start/db scripts (master is a devDependency)
├── master.config.js             # ports + frontend toggle (read by the CLI)
├── .env.example                 # NEXT_PUBLIC_API_URL, FRONTEND_URL, Section__Key examples
├── backend/
│   ├── package.json             # mastercontroller ^2.11, masterrecord ^1.22, socket.io; "test": node --test
│   ├── server.js                # boots the framework (config, scoped db, health, OpenAPI, auth, routes)
│   ├── app/
│   │   ├── routes.js            # URL → controller#action (+ authorize / typed params / names)
│   │   ├── controllers/         # API controllers: this.db, this.model, this.ok/created/notFound…
│   │   │   └── healthController.js
│   │   ├── models/
│   │   │   ├── AppContext.js    # registers entities (dbset) — one instance per request
│   │   │   ├── db.js            # withDb(fn) for work outside a request (jobs, scripts)
│   │   │   ├── User.js          # an example entity
│   │   │   └── db/migrations/   # generated migrations + context snapshot
│   │   └── sockets/             # WebSocket controllers
│   ├── middleware/              # auto-loaded pipeline (01-logger.js)
│   ├── config/
│   │   ├── appsettings.json             # layered configuration defaults
│   │   ├── appsettings.production.json  # production overrides (JSON logs, warn level)
│   │   └── environments/                # env.development.json, env.test.json, env.production.json
│   ├── db/                      # SQLite files (db/ and db/test/)
│   └── test/
│       └── health.test.js       # in-process integration test (master test)
└── frontend/
    ├── package.json             # next, react
    ├── next.config.mjs
    └── app/                     # Next.js App Router
        ├── layout.tsx
        ├── page.tsx             # welcome page — calls the backend /health
        └── lib/api.ts           # typed fetch helper

Key files#

backend/server.js#

The equivalent of an ASP.NET Core Program.cs. In order, it:

  1. Defaults NODE_ENV to development, sets master.root, master.environmentType and master.userSecretsId (the backend package name) so user secrets are read.
  2. Calls master.setupServer('http'), which builds the layered master.configuration (appsettings.jsonappsettings.<env>.jsonenv.<env>.json → user secrets → env vars → CLI args).
  3. Registers the database context per request: master.addScoped('db', AppContext). Controllers use this.db.
  4. Configures CORS for the frontend origin (FRONTEND_URL or Frontend:Url).
  5. Adds enterprise middleware: useResponseCompression(), useHealthChecks() with a database check, useOpenApi() (Swagger UI at /docs outside production), useGracefulShutdown().
  6. Registers JWT bearer auth + an AdminOnly policy only if Auth:JwtSecret (or JWT_SECRET) is configured.
  7. Auto-loads middleware/*.js alphabetically, then startMVC('app') (routes + controllers), start(server), optional sockets, and listen(PORT).
backend/server.js (the important lines)
process.env.NODE_ENV ??= 'development';
master.root = __dirname;
master.environmentType = process.env.NODE_ENV;
master.userSecretsId = pkg.name;

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

master.addScoped('db', AppContext);                 // this.db in controllers (scoped DbContext)

master.useResponseCompression();
master.useHealthChecks({
  path: '/health/report',
  checks: { database: { check: () => master.useScope((scope) => scope.db.canConnect()), tags: ['ready'] } },
});
master.useOpenApi({ title: `${pkg.name} API`, version: pkg.version, ui: master.environment.isProduction ? false : '/docs' });
master.useGracefulShutdown();

const jwtSecret = config.get('Auth:JwtSecret') || process.env.JWT_SECRET;
if (jwtSecret) {
  master.auth.addJwtBearer('bearer', { secret: jwtSecret, issuer: config.get('Auth:Issuer', pkg.name), expiresIn: config.get('Auth:ExpiresIn', '1h') });
  master.auth.addPolicy('AdminOnly', { roles: ['admin'] });
}

await master.startMVC('app');
await master.start(server);
server.listen(PORT);

backend/app/models/AppContext.js#

Your MasterRecord context. Generators keep the dbset list in sync.

AppContext.js
import context from 'masterrecord/context';
import User from './User.js';

class AppContext extends context {
  constructor() {
    super();
    this.env('config/environments');   // env.<NODE_ENV>.json, keyed by "AppContext"
    this.dbset(User);
  }
}

export default AppContext;

backend/app/models/db.js#

There is no shared database singleton. Inside a request you use this.db — a fresh AppContext created for that request and disposed when the response closes (a shared context would mix the change sets of concurrent requests; see Change Tracking & Context Lifetime). For work outside a request — jobs, scripts, hosted services — db.js exports withDb(fn), which runs fn inside a DI scope:

backend/app/models/db.js
import master from 'mastercontroller';

export function withDb(fn) {
  return master.useScope((scope) => fn(scope.db));
}

export default withDb;

// usage from a script or hosted service:
//   import { withDb } from './app/models/db.js';
//   await withDb(async (db) => { const users = await db.User.toList(); });

backend/app/controllers/healthController.js#

The tiny JSON endpoint the welcome page calls (GET /health). Load-balancer endpoints come from useHealthChecks: /health/live, /health/ready (runs the database check) and /health/report.

backend/config/#

appsettings.json + appsettings.production.json hold application settings; environments/env.<env>.json holds the database connection per environment. See Configuration.

backend/test/#

health.test.js boots the real pipeline in-process with master.createTestClient() and asserts on /health and /health/live. master test runs it with NODE_ENV=test. See Testing.

master.config.js#

Controls the dev/build/start orchestration — ports and whether a frontend exists.

master.config.js
export default {
  frontend: true,
  backendPort: 3001,
  frontendPort: 3000,
  backendDir: 'backend',
  frontendDir: 'frontend',
};

frontend/app/lib/api.ts#

A typed fetch helper that talks to the backend via NEXT_PUBLIC_API_URL.

Backend-only apps
Scaffold with --skip-frontend and you get just backend/ plus a master.config.js with frontend: false.
Built-in endpoints
Every new app answers /health, /health/live, /health/ready, /health/report, /openapi.json and (outside production) the Swagger UI at /docs before you write a line of code.