Overview
This document describes the structure of code generated by SolidX when you create a module, model, or fields. It explains the purpose of each file and how it is affected by user configuration through the SolidX Admin Panel.
Module Structure
When you generate a module in SolidX, it creates a basic NestJS module file to serve as the entry point for all logic related to that module.
├── fees-portal
│ ├── fees-portal.module.ts
This file is initially empty except for the standard @Module() metadata. Additional components like services, controllers, and entities are added as models are created.
Model Structure
Generating a model adds all necessary components for full CRUD functionality, including controllers, services, DTOs, entities, and repositories.
├── fees-portal
│ ├── controllers
│ │ ├── fee-type.controller.ts
│ ├── dtos
│ │ ├── create-fee-type.dto.ts
│ │ ├── update-fee-type.dto.ts
│ ├── entities
│ │ ├── fee-type.entity.ts
│ ├── repositories
│ │ ├── fee-type.repository.ts
│ ├── services
│ │ ├── fee-type.service.ts
Controller
The generated controller provides a full RESTful API interface to manage the model's records, with rich support for:
-
Create:
create: Add a single record with optional file uploads.insertMany: Bulk creation of multiple records.
-
Update:
update: Fully replace a record by ID.partialUpdate: Modify specific fields of a record.
-
Soft Delete & Recovery:
delete,deleteMany: Soft delete individual or multiple records.recover,recoverMany: Restore previously deleted records.
-
Read/Query:
findMany: Filterable and paginated list query supporting advanced filters, sorting, grouping, field selection, population, and soft delete visibility.findOne: Fetch a single record by ID.
Each endpoint is decorated for:
- JWT-based authorization via
@ApiBearerAuth("jwt") - Swagger documentation via
@ApiTagsand@ApiQuery - File uploads via
@UseInterceptors(AnyFilesInterceptor())
Click to view full sample controller code
import { Controller, Post, Body, Param, UploadedFiles, UseInterceptors, Put, Get, Query, Delete, Patch } from '@nestjs/common';
import { AnyFilesInterceptor } from "@nestjs/platform-express";
import { ApiBearerAuth, ApiQuery, ApiTags } from '@nestjs/swagger';
import { FeeTypeService } from '../services/fee-type.service';
import { CreateFeeTypeDto } from '../dtos/create-fee-type.dto';
import { UpdateFeeTypeDto } from '../dtos/update-fee-type.dto';
enum ShowSoftDeleted {
INCLUSIVE = "inclusive",
EXCLUSIVE = "exclusive",
}
@ApiTags('Fees Portal')
@Controller('fee-type')
export class FeeTypeController {
constructor(private readonly service: FeeTypeService) {}
@ApiBearerAuth("jwt")
@Post()
@UseInterceptors(AnyFilesInterceptor())
create(@Body() createDto: CreateFeeTypeDto, @UploadedFiles() files: Array<Express.Multer.File>) {
return this.service.create(createDto, files);
}
@ApiBearerAuth("jwt")
@Post('/bulk')
@UseInterceptors(AnyFilesInterceptor())
insertMany(@Body() createDtos: CreateFeeTypeDto[], @UploadedFiles() filesArray: Express.Multer.File[][] = []) {
return this.service.insertMany(createDtos, filesArray);
}
@ApiBearerAuth("jwt")
@Put(':id')
@UseInterceptors(AnyFilesInterceptor())
update(@Param('id') id: number, @Body() updateDto: UpdateFeeTypeDto, @UploadedFiles() files: Array<Express.Multer.File>) {
return this.service.update(id, updateDto, files);
}
@ApiBearerAuth("jwt")
@Patch(':id')
@UseInterceptors(AnyFilesInterceptor())
partialUpdate(@Param('id') id: number, @Body() updateDto: UpdateFeeTypeDto, @UploadedFiles() files: Array<Express.Multer.File>) {
return this.service.update(id, updateDto, files, true);
}
@ApiBearerAuth("jwt")
@Post('/bulk-recover')
async recoverMany(@Body() ids: number[]) {
return this.service.recoverMany(ids);
}
@ApiBearerAuth("jwt")
@Get('/recover/:id')
async recover(@Param('id') id: number) {
return this.service.recover(id);
}
@ApiBearerAuth("jwt")
@ApiQuery({ name: 'showSoftDeleted', required: false, enum: ShowSoftDeleted })
@ApiQuery({ name: 'limit', required: false, type: Number })
@ApiQuery({ name: 'offset', required: false, type: Number })
@ApiQuery({ name: 'fields', required: false, type: Array })
@ApiQuery({ name: 'sort', required: false, type: Array })
@ApiQuery({ name: 'groupBy', required: false, type: Array })
@ApiQuery({ name: 'populate', required: false, type: Array })
@ApiQuery({ name: 'populateMedia', required: false, type: Array })
@ApiQuery({ name: 'filters', required: false, type: Array })
@Get()
async findMany(@Query() query: any) {
return this.service.find(query);
}
@ApiBearerAuth("jwt")
@Get(':id')
async findOne(@Param('id') id: string, @Query() query: any) {
return this.service.findOne(+id, query);
}
@ApiBearerAuth("jwt")
@Delete('/bulk')
async deleteMany(@Body() ids: number[]) {
return this.service.deleteMany(ids);
}
@ApiBearerAuth("jwt")
@Delete(':id')
async delete(@Param('id') id: number) {
return this.service.delete(id);
}
}
Service
The generated service class contains the core business logic for the model and extends the generic CRUDService<T> provided by @solidxai/core.
This base class provides reusable functionality such as:
- CRUD operations
- Soft delete and recovery support
- Validation and type safety
- Smart query parsing
- File/media attachment handling
- Soft delete and recovery support
- Seamless integration with metadata-driven configuration
Click to view full sample service code
import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { DiscoveryService, ModuleRef } from "@nestjs/core";
import { EntityManager, Repository } from 'typeorm';
import { CRUDService } from '@solidxai/core';
import { ModelMetadataService } from '@solidxai/core';
import { ModuleMetadataService } from '@solidxai/core';
import { ConfigService } from '@nestjs/config';
import { FileService } from '@solidxai/core';
import { CrudHelperService } from '@solidxai/core';
import { FeeType } from '../entities/fee-type.entity';
import { FeeTypeRepository } from '../repositories/fee-type.repository';
@Injectable()
export class FeeTypeService extends CRUDService<FeeType>{
constructor(
readonly modelMetadataService: ModelMetadataService,
readonly moduleMetadataService: ModuleMetadataService,
readonly configService: ConfigService,
readonly fileService: FileService,
readonly discoveryService: DiscoveryService,
readonly crudHelperService: CrudHelperService,
@InjectEntityManager()
readonly entityManager: EntityManager,
readonly repo: FeeTypeRepository,
readonly moduleRef: ModuleRef
) {
super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'feeType', 'fees-portal', moduleRef);
}
}
DTOs (Create/Update)
DTOs (Data Transfer Objects) define the structure of data sent between client and server.
They include:
- Validation decorators from
class-validatorto enforce rules - API documentation decorators from
@nestjs/swagger CreateDTO includes all required fields for a new recordUpdateDTO includes all fields from theCreateDTO, but marked as optionalUpdateDTO also includes anidfield used for identifying the record to update
Click to view full sample Create DTO
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsBoolean } from 'class-validator';
export class CreateFeeTypeDto {
@IsNotEmpty()
@IsString()
@ApiProperty({ description: "The actual fee type. Eg. Tuition Fees, Bus Fees" })
feeType: string;
@IsOptional()
@IsInt()
@ApiProperty()
instituteId: number;
@IsString()
@IsOptional()
@ApiProperty()
instituteUserKey: string;
@IsNotEmpty()
@IsBoolean()
@ApiProperty()
partPaymentAllowed: boolean = false;
}
Click to view full sample Update DTO
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsBoolean } from 'class-validator';
import { IsOptional} from 'class-validator';
export class UpdateFeeTypeDto {
@IsOptional()
@IsInt()
id: number;
@IsOptional()
@IsString()
@ApiProperty({ description: "The actual fee type. Eg. Tuition Fees, Bus Fees" })
feeType: string;
@IsOptional()
@IsInt()
@ApiProperty()
instituteId: number;
@IsString()
@IsOptional()
@ApiProperty()
instituteUserKey: string;
@IsOptional()
@IsBoolean()
@ApiProperty()
partPaymentAllowed: boolean;
}
Entity
The entity defines the database schema and extends CommonEntity from @solidxai/core, providing audit fields like createdAt, updatedAt, and soft-delete support.
Each field in the model is translated into a @Column() definition, and relationships like @ManyToOne() are generated based on the field configuration within the SolidX Admin Panel.
Key features include:
- Column type and constraints generation (e.g.,
varchar,boolean,decimal) - Relationship mappings via TypeORM decorators
- Nullable and unique constraints
- Index support for optimized querying
Click to view entity code
import { CommonEntity } from '@solidxai/core';
import { Entity, Column, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Institute } from 'src/fees-portal/entities/institute.entity'
@Entity('fees_portal_fee_type')
@Index(["feeType", "deletedTracker"], { unique: true })
export class FeeType extends CommonEntity {
@Column({ type: "varchar" })
feeType: string;
@ManyToOne(() => Institute, { onDelete: "CASCADE", nullable: false })
@JoinColumn()
institute: Institute;
@Column({ type: "boolean", default: false })
partPaymentAllowed: boolean = false;
@Column({ type: "varchar", nullable: true })
latePaymentFeesType: string;
@Column({ type: "decimal", nullable: true })
latePaymentFees: number;
}
Repository
The repository class is responsible for handling all direct database interactions.
It extends SolidBaseRepository<T>, which provides:
- TypeORM integration with metadata-aware behavior
- Query-level access control using
SecurityRuleRepository - Support for contextual data via
RequestContextService - Custom SQL, joins, and advanced query builder patterns
This class is the right place to define custom database queries and overrides beyond standard CRUD operations.
Click to view repository code
import { Injectable } from '@nestjs/common';
import { RequestContextService, SecurityRuleRepository, SolidBaseRepository } from '@solidxai/core';
import { DataSource } from 'typeorm';
import { FeeType } from '../entities/fee-type.entity';
@Injectable()
export class FeeTypeRepository extends SolidBaseRepository<FeeType> {
constructor(
readonly dataSource: DataSource,
readonly requestContextService: RequestContextService,
readonly securityRuleRepository: SecurityRuleRepository,
) {
super(FeeType, dataSource, requestContextService, securityRuleRepository);
}
}
Field Addition & Modification Impact
Adding or modifying fields in a model updates the following files:
├── dtos
│ ├── create-fee-type.dto.ts
│ ├── update-fee-type.dto.ts
├── entities
│ ├── fee-type.entity.ts
All validation rules, types, and constraints are automatically injected into the DTOs and entity.
- In the Update DTO, fields are optional by default and also include a required
idfield. - The entity will receive a new column or modification based on the model configuration.
Adding a New Field (e.g., latePaymentFeesType: string)
Create DTO
+ @IsOptional()
+ @IsString()
+ @ApiProperty()
+ latePaymentFeesType: string;
Update DTO
+ @IsOptional()
+ @IsString()
+ @ApiProperty()
+ latePaymentFeesType: string;
Entity
+ @Column({ type: "varchar", nullable: true })
+ latePaymentFeesType: string;
Updating Field Type (e.g., latePaymentFeesType: string → latePaymentFeesType: number)
Create DTO
@IsOptional()
- @IsString()
+ @IsNumber()
@ApiProperty()
latePaymentFeesType: number;
Update DTO
@IsOptional()
- @IsString()
+ @IsNumber()
@ApiProperty()
latePaymentFeesType: number;
Entity
- @Column({ type: "varchar", nullable: true })
+ @Column({ type: "decimal", nullable: true })
latePaymentFeesType: number;
Layout Files
Each model includes auto-generated layout JSON files to define UI behavior in the SolidX UI. Below are examples of the default layouts for the Fee Type model.
List Layout
Defines table columns, sorting, filtering, and global search settings.
Refer to List View for full documentation.
Default list layout for the Fee Type model
Fee Type model{
"name": "feeType-list-view",
"displayName": "Fee Type",
"type": "list",
"context": "{}",
"moduleUserKey": "fees-portal",
"modelUserKey": "feeType",
"layout": {
"type": "list",
"attrs": {
"pagination": true,
"pageSizeOptions": [10, 25, 50],
"enableGlobalSearch": true,
"create": true,
"edit": true,
"delete": true
},
"children": [
{ "type": "field", "attrs": { "name": "id", "sortable": true, "filterable": true, "isSearchable": true } },
{ "type": "field", "attrs": { "name": "feeType", "sortable": true, "filterable": true, "isSearchable": true } },
{ "type": "field", "attrs": { "name": "partPaymentAllowed", "sortable": true, "filterable": true, "isSearchable": true } },
{ "type": "field", "attrs": { "name": "latePaymentFeesType", "sortable": true, "filterable": true, "isSearchable": true } },
{ "type": "field", "attrs": { "name": "latePaymentFees", "sortable": true, "filterable": true, "isSearchable": true } },
{ "type": "field", "attrs": { "name": "institute", "sortable": true, "filterable": true, "isSearchable": true } }
]
}
}
Form Layout
Defines how fields are grouped and rendered in create/edit screens.
Refer to Form View for full documentation.
Default form layout for the Fee Type model
Fee Type model{
"name": "feeType-form-view",
"displayName": "Fee Type",
"type": "form",
"context": "{}",
"moduleUserKey": "fees-portal",
"modelUserKey": "feeType",
"layout": {
"type": "form",
"attrs": { "name": "form-1", "label": "Fee Type", "className": "grid" },
"children": [
{
"type": "sheet",
"attrs": { "name": "sheet-1" },
"children": [
{
"type": "row",
"attrs": { "name": "row-1" },
"children": [
{
"type": "column",
"attrs": { "name": "group-1", "label": "Fee Type", "className": "col-6" },
"children": [
{ "type": "field", "attrs": { "name": "feeType" } },
{ "type": "field", "attrs": { "name": "partPaymentAllowed" } },
{ "type": "field", "attrs": { "name": "institute" } }
]
},
{
"type": "column",
"attrs": { "name": "group-2", "label": "Late Payment", "className": "col-6" },
"children": [
{ "type": "field", "attrs": { "name": "latePaymentFeesType" } },
{ "type": "field", "attrs": { "name": "latePaymentFees" } }
]
}
]
}
]
}
]
}
}
Layout Customization Recipes
Here are some common layout customizations you can apply:
- Multiple Tabs in Form Layout – Organize fields into logical sections.
- Custom Header Button in List View – Add shortcuts for user actions.
- Field-Specific Widgets – Use date pickers, dropdowns, or custom components.
- Role-Based Visibility – Conditionally show/hide fields based on user roles.