Generators API
@tgraph/backend-generator exposes high-level generator classes that orchestrate schema parsing, path resolution, file writers, and formatters. Each generator consumes the shared Config object and can be invoked from the CLI (tgraph api, tgraph dashboard, etc.) or from your own scripts.
ApiGenerator
Generates a complete NestJS feature module (DTOs, controller, service, module wiring, and supporting static files) for every Prisma model decorated with @tg_form().
Constructor
import type { Config } from '@tgraph/backend-generator';
import { ApiGenerator, loadConfig } from '@tgraph/backend-generator';
const config: Config = loadConfig(); // or build manually
const generator = new ApiGenerator(config);
Methods
generate(): Promise<void>
Runs the full backend workflow:
- Static file sync –
NestStaticGeneratorensures guards, decorators, interceptors, and DTO helpers exist underconfig.output.backend.staticFiles. - Schema parsing –
PrismaSchemaParser+PrismaRelationsParserread only the models that include@tg_form()and enrich them with display metadata. - Module discovery & creation –
ModulePathResolversearches every path inconfig.output.backend.modules.searchPaths. When a module does not exist, the generator prompts (or auto-confirms whenbehavior.nonInteractiveis true) and creates a module skeleton insideconfig.output.backend.modules.defaultRoot. - File generation – Controller, service, DTOs, module files, and supporting barrel exports are created or updated with suffix/prefix and guard rules derived from
config.api. - AppModule update –
NestAppModuleUpdaterinjects imports andimports: []entries between the sentinel comments, honouring any manual code. - Data provider update – When
config.dashboard.updateDataProvideris enabled, endpoint mappings are rebuilt viaDataProviderEndpointGeneratorand written intopaths.dataProvider(or the auto-discovered file).
The method throws when required paths cannot be resolved (missing schema, AppModule, data provider, etc.) so you can surface errors in CI.
Example
import { ApiGenerator, loadConfig } from '@tgraph/backend-generator';
async function main() {
const config = loadConfig(); // Uses tgraph.config.(ts|js)
const generator = new ApiGenerator(config);
await generator.generate();
}
main().catch((error) => {
console.error('Generation failed', error);
process.exit(1);
});
Generated Artifacts
src/features/<model>/create-<model>.<suffix>.dto.ts,update-…, plus module-scoped DTOssrc/features/<model>/<model>.<suffix>.service.tswith Prisma CRUD wired toconfig.input.prismaServicesrc/features/<model>/<model>.module.ts(created when missing) with controller/service added to the decoratorsapp.module.tsimports & module registration blocks between// AUTO-GENERATED …comments- Dashboard data provider endpoint map if enabled
- Static guard/decorator/interceptor files when they are missing
Configuration Touchpoints
config.input.schemaPath– Prisma source of truthconfig.input.prismaService– used to compute import statements inside generated servicesconfig.output.backend.modules– search paths plus default creation targetconfig.api.suffix,config.api.prefix, andconfig.api.authentication.*– influence naming, route prefixes, and guard importsconfig.behavior.nonInteractive– bypasses prompts for CI pipelines
NestServiceGenerator
Generates NestJS service files with CRUD operations, Prisma integration, and optional relation selects and unique field getters.
Constructor
import { NestServiceGenerator } from '@tgraph/backend-generator';
const generator = new NestServiceGenerator({
suffix: 'Admin',
models: allPrismaModels,
relationsInclude: 'all', // or ['author', 'comments']
});
Options
interface NestServiceGeneratorOptions {
suffix?: string; // Class name suffix (e.g., 'Admin')
utilsPath?: string; // Path to utility modules
workspaceRoot?: string; // Project root directory
prismaServicePath?: string; // Path to Prisma service
models?: PrismaModel[]; // All parsed models (for relation lookups)
relationsInclude?: 'all' | string[]; // Relations to include in selects
}
Generated Service Features
Standard CRUD Methods
Every generated service includes:
getAll(query)- Paginated search with filtering and sortinggetOne(id)- Find by primary keycreate(dto)- Create new recordupdate(id, dto)- Update existing recordupdateMany(ids, dto)- Bulk updatedeleteOne(id)- Delete single recorddeleteMany(ids)- Bulk deletegetSelectFields()- Returns Prisma select object
Unique Field Getters (New)
For each field marked with @unique in your Prisma schema, the generator creates a dedicated getter method:
// Prisma schema
model User {
id String @id @default(uuid())
email String @unique
phone String @unique
}
// Generated service methods
async getOneByEmail(email: string): Promise<User> {
const item = await this.prisma.user.findUnique({
where: { email },
select: this.getSelectFields(),
});
if (!item) {
throw new NotFoundException('User not found');
}
return item;
}
async getOneByPhone(phone: string): Promise<User> {
const item = await this.prisma.user.findUnique({
where: { phone },
select: this.getSelectFields(),
});
if (!item) {
throw new NotFoundException('User not found');
}
return item;
}
Usage in controllers:
@Get('by-email/:email')
async findByEmail(@Param('email') email: string) {
return this.userService.getOneByEmail(email);
}
Relation Selects (New)
When relationsInclude is configured, the getSelectFields() method includes relation data with their display fields:
Configuration:
// tgraph.config.ts
api: {
relations: {
include: ['author', 'comments'], // or 'all'
},
}
Generated select object:
// Without relations
getSelectFields() {
return {
id: true,
title: true,
content: true,
createdAt: true,
};
}
// With relations: ['author', 'comments']
getSelectFields() {
return {
id: true,
title: true,
content: true,
createdAt: true,
author: { select: { id: true, name: true } },
comments: { select: { id: true, text: true } },
};
}
The generator automatically determines the display field for each relation:
- Field marked with
@tg.displaydirective - Field marked with
@tg.labeldirective - Falls back to
id
Performance considerations:
- Using
'all'includes every relation, which may impact query performance - Specify only needed relations:
['author', 'tags'] - Relations are excluded from
excludeFieldsto prevent duplication
Example Generated Service
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '@/infrastructure/database/prisma.service';
import { CreateUserAdminDto, UpdateUserAdminDto } from '../dtos';
@Injectable()
export class UserAdminService {
constructor(private prisma: PrismaService) {}
async getAll(query: PaginatedSearchQueryDto) {
const { skip, take, where, orderBy } = buildPaginatedQuery(query, ['email', 'name']);
const [data, total] = await Promise.all([
this.prisma.user.findMany({
skip,
take,
where,
orderBy,
select: this.getSelectFields(),
}),
this.prisma.user.count({ where }),
]);
return { data, total, page: query.page, limit: query.limit };
}
async getOne(id: string) {
const item = await this.prisma.user.findUnique({
where: { id },
select: this.getSelectFields(),
});
if (!item) {
throw new NotFoundException('User not found');
}
return item;
}
async getOneByEmail(email: string) {
const item = await this.prisma.user.findUnique({
where: { email },
select: this.getSelectFields(),
});
if (!item) {
throw new NotFoundException('User not found');
}
return item;
}
async create(dto: CreateUserAdminDto) {
return this.prisma.user.create({
data: dto,
select: this.getSelectFields(),
});
}
async update(id: string, dto: UpdateUserAdminDto) {
return this.prisma.user.update({
where: { id },
data: dto,
select: this.getSelectFields(),
});
}
async deleteOne(id: string) {
return this.prisma.user.delete({ where: { id } });
}
private getSelectFields() {
return {
id: true,
email: true,
name: true,
role: true,
createdAt: true,
// Relations included based on config
profile: { select: { id: true, bio: true } },
};
}
}
Configuration Impact
config.api.suffix- Appended to class namesconfig.api.relations.include- Controls relation selectsconfig.input.prismaService- Used for import paths- Unique fields in Prisma schema - Generate dedicated getter methods
DashboardGenerator
Creates a React Admin resource (List/Edit/Create/Show/Studio) for every generated model, updates the dashboard entrypoint, writes directive metadata, and optionally regenerates client-side API types from Swagger.
Constructor
import { DashboardGenerator, loadConfig } from '@tgraph/backend-generator';
const generator = new DashboardGenerator(loadConfig());
Methods
generate(): Promise<void>
Execution flow:
- Schema parse – Shares the same parser stack as the API generator to guarantee matching metadata.
- Type generation – Attempts to run
swagger-typescript-apiagainst<dashboardRoot>/types/swagger.json. Missing files only raise warnings so local workflows are not blocked. - Resource folders – For each model,
ReactComponentsGeneratoremits List/Edit/Create/Show/Studio pages plus a barrelindex.ts. When a folder already exists you are prompted before it is deleted and regenerated. - Field directives –
fieldDirectives.generated.tsis regenerated so runtime widgets know which inputs to use. - App component update –
ProjectPathResolverlocatesApp.tsx(or the configured override) and injects<Resource>declarations and Studio routes while preserving manual code. - Formatting – All generated files are run through Prettier via
formatGeneratedFiles.
Example
import { DashboardGenerator, loadConfig } from '@tgraph/backend-generator';
const config = loadConfig();
await new DashboardGenerator(config).generate();
Notable Settings
config.output.dashboard.rootandconfig.output.dashboard.resourcesdefine where files are written.config.dashboard.componentslets you override default React Admin inputs/fields without touching generated files.config.dashboard.updateDataProvidercontrols whether the backend generator updates the data provider map after API generation (dashboard generator itself does not touch it).
DtoGenerator
Builds read-only response DTOs for every Prisma model—useful when you only need typed API responses or prefer to manage controllers/services manually.
Constructor
import { DtoGenerator, loadConfig } from '@tgraph/backend-generator';
const generator = new DtoGenerator(loadConfig());
Methods
generate(): void
- Clears and recreates
config.output.backend.dtos. - Parses the schema (all models are included, no
@tg_form()filter). - Uses
NestDtoGeneratorto emitModelResponseDtoclasses that include relations and enums. - Wraps every file with the “Auto-generated” banner from
tagAutoGenerated. - Writes an
index.tsbarrel export.
Because DTO generation is synchronous, wrap the call in try/catch if you need error reporting.
Example
try {
new DtoGenerator(loadConfig()).generate();
} catch (error) {
console.error('DTO generation failed', error);
}
Output Layout
src/dtos/generated/
├── user-response.dto.ts
├── project-response.dto.ts
└── index.ts
DataProviderEndpointGenerator
Utility generator used by ApiGenerator to keep the React Admin data provider’s endpoint map in sync. You can also import it directly when you need custom behavior.
Methods
extractCustomEndpoints(content: string): string– pulls out the user-managed section below// Custom endpoints, allowing automated sections to be rebuilt without losing overrides.generateEndpointMappings(models: PrismaModel[], getResourceName, getApiEndpoint)– converts resolvers into"resource": "endpoint"pairs using any naming strategy you provide (the CLI usesgetResourceNameandgetApiEndpointfromgenerator/utils/naming).buildEndpointMap(autoGeneratedMappings: string, customEndpoints: string): string– produces the finalconst endpointMapstring that is written back todataProvider.ts.
Example
import { DataProviderEndpointGenerator } from '@tgraph/backend-generator';
const generator = new DataProviderEndpointGenerator();
const custom = generator.extractCustomEndpoints(existingFileContent);
const autogenerated = generator.generateEndpointMappings(
models,
(modelName) => modelName.toLowerCase(), // resource name resolver
(modelName) => `tg-api/${modelName.toLowerCase()}s`, // endpoint resolver
);
const nextContent = generator.buildEndpointMap(autogenerated, custom);
Running Generators Together
When you need all artifacts, run the generators sequentially so dependent assets exist in time:
import { ApiGenerator, DashboardGenerator, DtoGenerator, loadConfig } from '@tgraph/backend-generator';
async function generateEverything() {
const config = loadConfig();
await new ApiGenerator(config).generate();
await new DashboardGenerator(config).generate();
new DtoGenerator(config).generate();
}
generateEverything();
Next Steps
- Understand how parsers populate metadata: Parsers API
- See how helpers locate files and load configuration: Utilities
- Review every configuration option: Configuration Reference