Content is user-generated and unverified.
// src/decorators/auto-crud.decorator.ts import { SetMetadata } from '@nestjs/common'; export interface AutoCrudOptions { apiPath?: string; enableFilters?: boolean; enablePagination?: boolean; enableSorting?: boolean; enablePopulate?: boolean; excludeFields?: string[]; customValidation?: any; } export const AUTO_CRUD_KEY = 'auto-crud'; export const AutoCrud = (options: AutoCrudOptions = {}) => SetMetadata(AUTO_CRUD_KEY, options); // src/interfaces/query.interface.ts export interface FilterQuery { [key: string]: any; } export interface PopulateQuery { [key: string]: boolean | PopulateQuery; } export interface PaginationQuery { page?: number; limit?: number; } export interface SortQuery { [key: string]: 'ASC' | 'DESC'; } export interface BaseQueryDto { filters?: FilterQuery; locale?: string; status?: string; populate?: string | PopulateQuery; fields?: string[]; sort?: string | SortQuery; pagination?: PaginationQuery; } // src/dto/base-query.dto.ts import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsOptional, IsString, IsObject, IsArray, IsNumber, Min } from 'class-validator'; import { Transform, Type } from 'class-transformer'; export class BaseQueryDto { @ApiPropertyOptional({ description: 'Filter the response', example: { name: 'John', age: { $gte: 18 } } }) @IsOptional() @IsObject() @Transform(({ value }) => typeof value === 'string' ? JSON.parse(value) : value) filters?: any; @ApiPropertyOptional({ description: 'Select a locale', example: 'en' }) @IsOptional() @IsString() locale?: string; @ApiPropertyOptional({ description: 'Select the Draft & Publish status', example: 'published' }) @IsOptional() @IsString() status?: string; @ApiPropertyOptional({ description: 'Populate relations, components, or dynamic zones', example: 'user,category' }) @IsOptional() populate?: string; @ApiPropertyOptional({ description: 'Select only specific fields to display', example: ['name', 'email'] }) @IsOptional() @IsArray() @Transform(({ value }) => typeof value === 'string' ? value.split(',') : value) fields?: string[]; @ApiPropertyOptional({ description: 'Sort the response', example: 'name:asc,createdAt:desc' }) @IsOptional() sort?: string; @ApiPropertyOptional({ description: 'Page number', example: 1 }) @IsOptional() @Type(() => Number) @IsNumber() @Min(1) page?: number; @ApiPropertyOptional({ description: 'Number of items per page', example: 10 }) @IsOptional() @Type(() => Number) @IsNumber() @Min(1) limit?: number; } // src/services/base-crud.service.ts import { Injectable } from '@nestjs/common'; import { Repository, SelectQueryBuilder, ObjectLiteral } from 'typeorm'; import { BaseQueryDto } from '../dto/base-query.dto'; @Injectable() export class BaseCrudService<T extends ObjectLiteral> { constructor(private readonly repository: Repository<T>) {} async findAll(queryDto: BaseQueryDto): Promise<{ data: T[]; meta: any }> { const queryBuilder = this.repository.createQueryBuilder('entity'); // Apply filters if (queryDto.filters) { this.applyFilters(queryBuilder, queryDto.filters); } // Apply populate/relations if (queryDto.populate) { this.applyPopulate(queryBuilder, queryDto.populate); } // Apply sorting if (queryDto.sort) { this.applySorting(queryBuilder, queryDto.sort); } // Apply field selection if (queryDto.fields && queryDto.fields.length > 0) { const fields = queryDto.fields.map(field => `entity.${field}`); queryBuilder.select(fields); } // Apply pagination const page = queryDto.page || 1; const limit = queryDto.limit || 10; const offset = (page - 1) * limit; const totalCount = await queryBuilder.getCount(); const data = await queryBuilder.skip(offset).take(limit).getMany(); const meta = { pagination: { page, pageSize: limit, pageCount: Math.ceil(totalCount / limit), total: totalCount } }; return { data, meta }; } async findOne(id: string | number, populate?: string): Promise<T> { const queryBuilder = this.repository.createQueryBuilder('entity'); queryBuilder.where('entity.id = :id', { id }); if (populate) { this.applyPopulate(queryBuilder, populate); } return queryBuilder.getOne(); } async create(createDto: Partial<T>): Promise<T> { const entity = this.repository.create(createDto); return this.repository.save(entity); } async update(id: string | number, updateDto: Partial<T>): Promise<T> { await this.repository.update(id, updateDto); return this.findOne(id); } async remove(id: string | number): Promise<void> { await this.repository.delete(id); } private applyFilters(queryBuilder: SelectQueryBuilder<T>, filters: any): void { Object.keys(filters).forEach((key, index) => { const value = filters[key]; const paramKey = `filter_${key}_${index}`; if (typeof value === 'object' && value !== null) { // Handle operators like $gte, $lte, $like, etc. Object.keys(value).forEach((operator, opIndex) => { const opValue = value[operator]; const opParamKey = `${paramKey}_${opIndex}`; switch (operator) { case '$gte': queryBuilder.andWhere(`entity.${key} >= :${opParamKey}`, { [opParamKey]: opValue }); break; case '$lte': queryBuilder.andWhere(`entity.${key} <= :${opParamKey}`, { [opParamKey]: opValue }); break; case '$gt': queryBuilder.andWhere(`entity.${key} > :${opParamKey}`, { [opParamKey]: opValue }); break; case '$lt': queryBuilder.andWhere(`entity.${key} < :${opParamKey}`, { [opParamKey]: opValue }); break; case '$like': queryBuilder.andWhere(`entity.${key} LIKE :${opParamKey}`, { [opParamKey]: `%${opValue}%` }); break; case '$in': queryBuilder.andWhere(`entity.${key} IN (:...${opParamKey})`, { [opParamKey]: opValue }); break; case '$nin': queryBuilder.andWhere(`entity.${key} NOT IN (:...${opParamKey})`, { [opParamKey]: opValue }); break; default: queryBuilder.andWhere(`entity.${key} = :${opParamKey}`, { [opParamKey]: opValue }); } }); } else { queryBuilder.andWhere(`entity.${key} = :${paramKey}`, { [paramKey]: value }); } }); } private applyPopulate(queryBuilder: SelectQueryBuilder<T>, populate: string): void { const relations = populate.split(',').map(rel => rel.trim()); relations.forEach(relation => { queryBuilder.leftJoinAndSelect(`entity.${relation}`, relation); }); } private applySorting(queryBuilder: SelectQueryBuilder<T>, sort: string): void { const sortPairs = sort.split(',').map(s => s.trim()); sortPairs.forEach(pair => { const [field, direction] = pair.split(':'); const sortDirection = direction?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC'; queryBuilder.addOrderBy(`entity.${field}`, sortDirection); }); } } // src/controllers/base-crud.controller.ts import { Controller, Get, Post, Put, Delete, Param, Body, Query, HttpCode, HttpStatus } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody } from '@nestjs/swagger'; import { BaseCrudService } from '../services/base-crud.service'; import { BaseQueryDto } from '../dto/base-query.dto'; export function createBaseCrudController<T>( entityName: string, createDto: any, updateDto: any, entityClass: any ) { @ApiTags(entityName) @Controller() class BaseCrudController { constructor(private readonly service: BaseCrudService<T>) {} @Get() @ApiOperation({ summary: `Get a list of ${entityName}` }) @ApiResponse({ status: 200, description: `List of ${entityName} retrieved successfully`, schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: `#/components/schemas/${entityClass.name}` } }, meta: { type: 'object', properties: { pagination: { type: 'object', properties: { page: { type: 'number' }, pageSize: { type: 'number' }, pageCount: { type: 'number' }, total: { type: 'number' } } } } } } } }) async findAll(@Query() query: BaseQueryDto) { return this.service.findAll(query); } @Get(':id') @ApiOperation({ summary: `Get a ${entityName}` }) @ApiParam({ name: 'id', description: `${entityName} ID` }) @ApiResponse({ status: 200, description: `${entityName} retrieved successfully`, type: entityClass }) async findOne( @Param('id') id: string, @Query('populate') populate?: string ) { return this.service.findOne(id, populate); } @Post() @ApiOperation({ summary: `Create a ${entityName}` }) @ApiBody({ type: createDto }) @ApiResponse({ status: 201, description: `${entityName} created successfully`, type: entityClass }) async create(@Body() createEntityDto: any) { return this.service.create(createEntityDto); } @Put(':id') @ApiOperation({ summary: `Update a ${entityName}` }) @ApiParam({ name: 'id', description: `${entityName} ID` }) @ApiBody({ type: updateDto }) @ApiResponse({ status: 200, description: `${entityName} updated successfully`, type: entityClass }) async update( @Param('id') id: string, @Body() updateEntityDto: any ) { return this.service.update(id, updateEntityDto); } @Delete(':id') @HttpCode(HttpStatus.NO_CONTENT) @ApiOperation({ summary: `Delete a ${entityName}` }) @ApiParam({ name: 'id', description: `${entityName} ID` }) @ApiResponse({ status: 204, description: `${entityName} deleted successfully` }) async remove(@Param('id') id: string) { return this.service.remove(id); } } return BaseCrudController; } // src/utils/module-generator.ts import { Module, DynamicModule, Type } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { BaseCrudService } from '../services/base-crud.service'; import { createBaseCrudController } from '../controllers/base-crud.controller'; export interface CrudModuleOptions { entity: any; createDto: any; updateDto: any; apiPath?: string; entityName?: string; } export function createCrudModule(options: CrudModuleOptions): DynamicModule { const { entity, createDto, updateDto, apiPath, entityName } = options; const serviceName = `${entity.name}CrudService`; const controllerName = `${entity.name}CrudController`; const CrudService = class extends BaseCrudService<any> {}; Object.defineProperty(CrudService, 'name', { value: serviceName }); const CrudController = createBaseCrudController( entityName || entity.name.toLowerCase(), createDto, updateDto, entity ); Object.defineProperty(CrudController, 'name', { value: controllerName }); return { module: class CrudModule {}, imports: [TypeOrmModule.forFeature([entity])], controllers: [CrudController], providers: [ { provide: serviceName, useFactory: (repository) => new CrudService(repository), inject: [`${entity.name}Repository`] } ], exports: [serviceName] }; } // Example usage: // src/entities/user.entity.ts import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { Post } from './post.entity'; @Entity() export class User { @ApiProperty({ description: 'User ID' }) @PrimaryGeneratedColumn() id: number; @ApiProperty({ description: 'User name' }) @Column() name: string; @ApiProperty({ description: 'User email' }) @Column({ unique: true }) email: string; @ApiProperty({ description: 'User age' }) @Column() age: number; @ApiProperty({ description: 'User posts' }) @OneToMany(() => Post, post => post.user) posts: Post[]; @ApiProperty({ description: 'Creation date' }) @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) createdAt: Date; @ApiProperty({ description: 'Update date' }) @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' }) updatedAt: Date; } // src/dto/create-user.dto.ts import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsEmail, IsNumber, Min } from 'class-validator'; export class CreateUserDto { @ApiProperty({ description: 'User name', example: 'John Doe' }) @IsString() name: string; @ApiProperty({ description: 'User email', example: 'john@example.com' }) @IsEmail() email: string; @ApiProperty({ description: 'User age', example: 25 }) @IsNumber() @Min(1) age: number; } // src/dto/update-user.dto.ts import { PartialType } from '@nestjs/swagger'; import { CreateUserDto } from './create-user.dto'; export class UpdateUserDto extends PartialType(CreateUserDto) {} // src/modules/user.module.ts import { Module } from '@nestjs/common'; import { createCrudModule } from '../utils/module-generator'; import { User } from '../entities/user.entity'; import { CreateUserDto } from '../dto/create-user.dto'; import { UpdateUserDto } from '../dto/update-user.dto'; @Module({}) export class UserModule { static forRoot() { return createCrudModule({ entity: User, createDto: CreateUserDto, updateDto: UpdateUserDto, apiPath: 'users', entityName: 'User' }); } } // src/app.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserModule } from './modules/user.module'; import { User } from './entities/user.entity'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', // or your database type host: 'localhost', port: 5432, username: 'your_username', password: 'your_password', database: 'your_database', entities: [User], synchronize: true, // Don't use in production }), UserModule.forRoot(), ], }) export class AppModule {} // src/main.ts import { NestFactory } from '@nestjs/core'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { ValidationPipe } from '@nestjs/common'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // Enable validation app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true, })); // Setup Swagger const config = new DocumentBuilder() .setTitle('Auto CRUD API') .setDescription('Automatically generated CRUD API with advanced filtering') .setVersion('1.0') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, document); await app.listen(3000); console.log('Application is running on: http://localhost:3000'); console.log('Swagger documentation: http://localhost:3000/api/docs'); } bootstrap(); /* USAGE EXAMPLES: 1. Get all users with filters: GET /api/users?filters={"age":{"$gte":18}}&sort=name:asc&page=1&limit=10 2. Get all users with population: GET /api/users?populate=posts&fields=name,email 3. Create a user: POST /api/users { "name": "John Doe", "email": "john@example.com", "age": 25 } 4. Update a user: PUT /api/users/1 { "name": "Jane Doe" } 5. Delete a user: DELETE /api/users/1 FEATURES: - ✅ Automatic CRUD endpoints - ✅ Advanced filtering with operators ($gte, $lte, $like, $in, etc.) - ✅ Pagination support - ✅ Sorting support - ✅ Field selection - ✅ Relation population - ✅ Swagger documentation - ✅ Validation with class-validator - ✅ TypeScript support - ✅ Extensible architecture To add a new entity, simply: 1. Create the entity class 2. Create DTOs 3. Create a module using createCrudModule() 4. Import the module in AppModule */
Content is user-generated and unverified.
    NestJS TypeORM Auto-CRUD System | Claude