Prisma Schema Setup
Learn how to configure your Prisma schema for optimal code generation with TGraph Backend Generator.
Model Annotation
Mark models for generation with the @tg_form() comment:
// @tg_form()
model User {
id String @id @default(uuid())
name String
email String @unique
}
Only models with // @tg_form() are processed. This gives you fine-grained control over what gets generated.
Display Labels for Relations
Control how related records display in dropdowns and references with @tg_label:
// @tg_label(name)
// @tg_form()
model User {
id String @id @default(uuid())
name String
email String @unique
}
// @tg_form()
model Post {
id String @id @default(uuid())
title String
author User @relation(fields: [authorId], references: [id])
authorId String
}
In the Post form, the author dropdown will display user names instead of IDs.
Label Field Fallback
If no @tg_label is specified, the generator uses this heuristic:
nametitleemailcodeslug- First non-ID string field
id(fallback)
Field Directives
Field directives control how individual fields are generated. Place them in triple-slash comments immediately above the field:
model User {
id String @id @default(uuid())
name String
/// @tg_format(email)
email String @unique
/// @tg_format(url)
avatar String?
/// @tg_readonly
ipAddress String?
}
See the Field Directives Guide for complete reference.
Custom Validation
Add validation through inline comments using class-validator syntax:
Max Length
model User {
firstName String // @max(50)
lastName String // @max(50)
bio String? // @max(500)
}
Generates:
export class CreateUserTgDto {
@IsString()
@MaxLength(50)
@IsNotEmpty()
firstName: string;
@IsString()
@MaxLength(50)
@IsNotEmpty()
lastName: string;
@IsString()
@MaxLength(500)
@IsOptional()
bio?: string;
}
Min Length
model User {
username String // @min(3)
password String // @min(8)
}
Exact Length
model User {
zipCode String // @length(5)
}
Pattern Validation
model User {
phone String // @pattern(/^[0-9]{10}$/)
}
Operation-Specific Validation
Apply validation only for specific operations:
model User {
password String // @min(8, [create])
bio String? // @max(500, [create, update])
}
Valid operations: create, update.
Enum Support
Enums are fully supported with automatic validation:
// @tg_form()
model User {
id String @id @default(uuid())
name String
role Role @default(USER)
status Status @default(ACTIVE)
}
enum Role {
USER
AUTHOR
ADMIN
}
enum Status {
ACTIVE
INACTIVE
SUSPENDED
}
Generates:
DTO:
export class CreateUserTgDto {
@IsString()
@IsNotEmpty()
name: string;
@IsEnum(Role)
@IsOptional()
role?: Role;
@IsEnum(Status)
@IsOptional()
status?: Status;
}
Dashboard:
<SelectInput
source="role"
choices={[
{ id: 'USER', name: 'USER' },
{ id: 'AUTHOR', name: 'AUTHOR' },
{ id: 'ADMIN', name: 'ADMIN' },
]}
/>
Relations
One-to-Many
// @tg_label(name)
// @tg_form()
model User {
id String @id @default(uuid())
name String
posts Post[]
}
// @tg_label(title)
// @tg_form()
model Post {
id String @id @default(uuid())
title String
author User @relation(fields: [authorId], references: [id])
authorId String
@@index([authorId])
}
Generates a ReferenceInput in the Post form for selecting the author.
Many-to-Many
// @tg_form()
model Post {
id String @id @default(uuid())
title String
categories Category[]
}
// @tg_form()
model Category {
id String @id @default(uuid())
name String
posts Post[]
}
Generates ReferenceArrayInput for multi-select.
Optional vs Required Fields
The generator respects Prisma’s optionality:
model User {
required String // Required field
optional String? // Optional field
withDefault String @default("default") // Has default (optional in create)
}
Create DTO:
export class CreateUserTgDto {
@IsString()
@IsNotEmpty()
required: string;
@IsString()
@IsOptional()
optional?: string;
@IsString()
@IsOptional()
withDefault?: string;
}
Unique Constraints
Unique constraints are respected in generated services:
model User {
id String @id @default(uuid())
email String @unique
}
The generated service handles unique constraint violations:
async create(dto: CreateUserTgDto): Promise<User> {
try {
return await this.prisma.user.create({ data: dto });
} catch (error) {
if (error.code === 'P2002') {
throw new ConflictException('Email already exists');
}
throw error;
}
}
Excluded Fields
Certain fields are automatically excluded from TG APIs:
- Password fields – Any field named
password,passwordHash,hashedPassword - Internal fields – System fields you mark as excluded
To manually exclude a field, don’t use @tg_form() on that model or handle it in custom logic.
DateTime Fields
DateTime fields are automatically handled:
model Post {
id String @id @default(uuid())
title String
publishedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Dashboard:
publishedAt→DateTimeInput(user-editable)createdAt→ Read-only displayupdatedAt→ Auto-managed by Prisma
JSON Fields
model Settings {
id String @id @default(uuid())
metadata Json?
}
Generates:
- DTO:
metadata?: any - Dashboard:
JsonInputcomponent with validation
Default Values
Fields with defaults are optional in Create DTOs:
model User {
id String @id @default(uuid())
role Role @default(USER)
isActive Boolean @default(true)
createdAt DateTime @default(now())
}
Create DTO:
export class CreateUserTgDto {
@IsEnum(Role)
@IsOptional()
role?: Role; // Optional because it has a default
@IsBoolean()
@IsOptional()
isActive?: boolean; // Optional
// createdAt excluded - managed by Prisma
}
Composite Indexes
While not directly affecting generation, composite indexes are respected:
model Post {
id String @id @default(uuid())
authorId String
status Status
@@index([authorId, status])
}
The generated search queries leverage these indexes.
Best Practices
1. Use Meaningful Names
// Good
model BlogPost {
id String @id
title String
content String
}
// Avoid
model BP {
i String @id
t String
c String
}
2. Add Label Hints
// @tg_label(name)
// @tg_form()
model User {
id String @id
name String
}
3. Document Complex Fields
model User {
/// @tg_format(url)
/// User's profile photo URL
avatar String?
}
4. Use Enums for Constrained Values
// Good
enum Status {
ACTIVE
INACTIVE
}
model User {
status Status @default(ACTIVE)
}
// Avoid
model User {
status String @default("active") // No validation
}
5. Add Validation in Comments
model User {
email String @unique // @pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
username String // @min(3) @max(20)
}
6. Use Relations Instead of Raw IDs
// Good
model Post {
author User @relation(fields: [authorId], references: [id])
authorId String
}
// Avoid manually managing relations
Example: Complete Model
// @tg_label(name)
// @tg_form()
model User {
id String @id @default(uuid())
name String // @min(2) @max(100)
/// @tg_format(email)
email String @unique
/// @tg_format(url)
avatar String?
/// @tg_format(tel)
phone String?
role Role @default(USER)
isActive Boolean @default(true)
posts Post[]
/// @tg_readonly
ipAddress String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
// @tg_label(title)
// @tg_form()
model Post {
id String @id @default(uuid())
title String // @min(5) @max(200)
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
/// @tg_upload(image)
coverImage String?
publishedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
@@index([published, publishedAt])
}
enum Role {
USER
AUTHOR
ADMIN
}
This generates a complete, production-ready admin system with validation, file uploads, and relation management.
Next Steps
- Field Directives – Learn all available field directives
- Custom Validation – Advanced validation patterns
- Naming Conventions – Understand how names are transformed