Generator
Rhino includes an interactive CLI that scaffolds resource stubs with the Rhino conventions in place. Generate a model stub, a policy, a scope, or a request class in seconds.
Usage
npx rhino generate
The command launches an interactive prompt:
Rhino :: Generate
? What would you like to generate? (model | policy | scope | request)
? Name (PascalCase, e.g. "BlogPost"):
What It Generates
The generator can create four types of stubs:
Model
Generates a @RhinoModel()-decorated class stub. You are prompted for the columns (comma-separated name:type pairs):
? Columns (comma-separated, e.g. "title:string, body:text, published:boolean"):
Generated file: src/models/{Name}.model.ts
/**
* BlogPost model stub — generated by `npx rhino generate`
* Register in rhino.config.ts:
* blogPosts: { model: 'BlogPost' }
*/
import { RhinoModel } from '@rhino-dev/rhino-nestjs';
@RhinoModel()
export class BlogPost {
id!: number;
title?: string;
body?: string;
published?: boolean;
createdAt?: Date;
updatedAt?: Date;
}
The @RhinoModel() decorator is an optional alternative to registering the model directly in src/rhino.config.ts. The config-based models: { slug: { model: '...' } } approach is the primary path; see Models. The generator does not add the entry to your config — register the new model yourself (the stub's header comment shows the line to add).
Policy
Generates a policy class that extends ResourcePolicy:
Generated file: src/policies/{Name}.policy.ts
/**
* Post policy stub — generated by `npx rhino generate`
* Extend ResourcePolicy and override the methods you need.
*/
import { ResourcePolicy } from '@rhino-dev/rhino-nestjs';
export class PostPolicy extends ResourcePolicy {
// override viewAny(user: any, org?: any): boolean { ... }
// override view(user: any, record: any, org?: any): boolean { ... }
// override create(user: any, org?: any): boolean { ... }
// override update(user: any, record: any, org?: any): boolean { ... }
// override delete(user: any, record: any, org?: any): boolean { ... }
}
Scope
Generates a scope class implementing RhinoScope:
Generated file: src/scopes/{Name}.scope.ts
/**
* Post scope stub — generated by `npx rhino generate`
* Implement the `apply` method to filter / order the default query.
*/
import type { RhinoScope } from '@rhino-dev/rhino-nestjs';
export class PostScope implements RhinoScope {
apply(query: Record<string, any>, _context: any): Record<string, any> {
// Example: return { ...query, where: { ...query.where, deletedAt: null } };
return query;
}
}
Request
Generates a request class — the validation for one write action on one model. You are asked which action(s) to generate:
? Which action(s)? (store | update | both)
Generated file: src/requests/{name}-{action}.request.ts
/**
* PostStoreRequest — generated by `npx rhino generate`
*
* IMPORTANT: the parsed output IS the write payload. A field with no rule in
* `rules()` is silently dropped and never persisted — declare every field you
* want written, including optional ones.
*/
import { z } from 'zod';
import {
ResourceRequest,
type ResourceRequestContext,
} from '@rhino-dev/rhino-nestjs';
export class PostStoreRequest extends ResourceRequest {
rules(ctx: ResourceRequestContext) {
return z.object({
// title: z.string().min(1).max(255),
});
}
}
The real stub is heavily commented: it shows authorize() and prepare(), a role-dependent rule, a route-group-dependent rule and — on the update class — a rule that reads ctx.record. After writing the files the generator prints the registration line to paste into src/rhino.config.ts:
Register on the model:
posts: { model: 'Post', requests: { store: PostStoreRequest, update: PostUpdateRequest } }
Unlike Laravel and Rails, NestJS has no filesystem discovery — a request class runs only once it is registered.
Interactive Walkthrough
Step 1: Choose what to generate
? What would you like to generate?
> model
policy
scope
request
Step 2: Enter the name
? Name (PascalCase, e.g. "BlogPost"): Post
Use PascalCase singular (e.g., Post, not posts). The generator derives the file name from the name.
Step 3 (model only): Define columns
? Columns (comma-separated, e.g. "title:string, body:text, published:boolean"): title:string, body:text
Each column is a name:type pair. Recognized types map to TypeScript types in the generated stub:
| Column type | TypeScript type |
|---|---|
string, text, uuid | string |
integer, bigInteger, decimal, float | number |
boolean | boolean |
date, datetime, timestamp | Date |
If a stub file already exists at the target path, the generator skips it rather than overwriting.
After Generation
The generator only writes a stub. To wire it up:
- Add the column(s) to your
prisma/schema.prismamodel and runnpx prisma migrate dev. - Register the model in
src/rhino.config.ts(the stub header comment shows the entry to add). - Attach the policy, scope and/or request classes to the registration via
policy:,scopes:andrequests:.
For end-to-end generation (Prisma models, policies, tests, and seeders all at once), see the Blueprint system.