Skip to main content

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 @ApiTags and @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-validator to enforce rules
  • API documentation decorators from @nestjs/swagger
  • Create DTO includes all required fields for a new record
  • Update DTO includes all fields from the Create DTO, but marked as optional
  • Update DTO also includes an id field 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 id field.
  • 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: stringlatePaymentFeesType: 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

{
"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

{
"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.