UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

1,379 lines (1,204 loc) โ€ข 37.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.nestjsTemplate = void 0; exports.nestjsTemplate = { id: 'nestjs', name: 'nestjs', displayName: 'NestJS', description: 'Progressive Node.js framework for building efficient, scalable server-side applications', language: 'typescript', framework: 'nestjs', version: '10.3.8', tags: ['nodejs', 'nestjs', 'api', 'rest', 'graphql', 'microservices', 'typescript'], port: 3000, dependencies: {}, features: ['dependency-injection', 'modular-architecture', 'graphql', 'websocket', 'microservices', 'testing', 'swagger', 'validation'], files: { // Package configuration 'package.json': `{ "name": "{{projectName}}", "version": "0.0.1", "description": "NestJS API server with modular architecture", "author": "", "private": true, "license": "MIT", "scripts": { "build": "nest build", "format": "prettier --write \\"src/**/*.ts\\" \\"test/**/*.ts\\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \\"{src,apps,libs,test}/**/*.ts\\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json", "typecheck": "tsc --noEmit", "migration:generate": "npm run typeorm -- migration:generate", "migration:run": "npm run typeorm -- migration:run", "migration:revert": "npm run typeorm -- migration:revert", "typeorm": "typeorm-ts-node-commonjs -d src/config/data-source.ts" }, "dependencies": { "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", "@nestjs/core": "^10.3.8", "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "^2.0.5", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.3.8", "@nestjs/platform-socket.io": "^10.3.8", "@nestjs/platform-ws": "^10.3.8", "@nestjs/swagger": "^7.3.1", "@nestjs/terminus": "^10.2.3", "@nestjs/throttler": "^5.1.2", "@nestjs/typeorm": "^10.0.2", "@nestjs/websockets": "^10.3.8", "@nestjs/cache-manager": "^2.2.2", "@nestjs/bull": "^10.1.1", "@nestjs/schedule": "^4.0.2", "@nestjs/serve-static": "^4.0.2", "@nestjs/microservices": "^10.3.8", "@nestjs/graphql": "^12.1.1", "@nestjs/apollo": "^12.1.0", "@apollo/server": "^4.10.4", "graphql": "^16.8.1", "apollo-server-express": "^3.13.0", "bcryptjs": "^2.4.3", "bull": "^4.12.2", "cache-manager": "^5.5.1", "cache-manager-redis-store": "^3.0.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "compression": "^1.7.4", "cookie-parser": "^1.4.6", "dotenv": "^16.4.5", "helmet": "^7.1.0", "ioredis": "^5.3.2", "joi": "^17.12.3", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "pg": "^8.11.5", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "typeorm": "^0.3.20", "uuid": "^9.0.1", "winston": "^3.13.0", "winston-daily-rotate-file": "^5.0.0", "multer": "^1.4.5-lts.1", "@types/multer": "^1.4.11", "nodemailer": "^6.9.13", "handlebars": "^4.7.8", "amqplib": "^0.10.4", "kafkajs": "^2.2.4", "redis": "^4.6.13" }, "devDependencies": { "@nestjs/cli": "^10.3.2", "@nestjs/schematics": "^10.1.1", "@nestjs/testing": "^10.3.8", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "@types/node": "^20.12.7", "@types/supertest": "^6.0.2", "@types/bcryptjs": "^2.4.6", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", "@types/bull": "^4.10.0", "@types/cache-manager": "^4.0.6", "@types/cookie-parser": "^1.4.7", "@types/nodemailer": "^6.4.14", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "prettier": "^3.2.5", "source-map-support": "^0.5.21", "supertest": "^7.0.0", "ts-jest": "^29.1.2", "ts-loader": "^9.5.1", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typescript": "^5.4.5" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\\\.spec\\\\.ts$", "transform": { "^.+\\\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } }`, // TypeScript configuration 'tsconfig.json': `{ "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": true, "noImplicitAny": true, "strictBindCallApply": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "esModuleInterop": true, "resolveJsonModule": true, "paths": { "@/*": ["src/*"], "@auth/*": ["src/auth/*"], "@common/*": ["src/common/*"], "@config/*": ["src/config/*"], "@database/*": ["src/database/*"], "@modules/*": ["src/modules/*"], "@shared/*": ["src/shared/*"] } }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] }`, // Nest CLI configuration 'nest-cli.json': `{ "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", "sourceRoot": "src", "compilerOptions": { "deleteOutDir": true, "webpack": true, "tsConfigPath": "tsconfig.build.json" }, "generateOptions": { "spec": true } }`, // Build configuration 'tsconfig.build.json': `{ "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] }`, // Main application entry 'src/main.ts': `import { NestFactory } from '@nestjs/core'; import { ValidationPipe, VersioningType } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { NestExpressApplication } from '@nestjs/platform-express'; import * as compression from 'compression'; import * as cookieParser from 'cookie-parser'; import helmet from 'helmet'; import { WinstonModule } from 'nest-winston'; import { join } from 'path'; import { AppModule } from './app.module'; import { HttpExceptionFilter } from './common/filters/http-exception.filter'; import { TransformInterceptor } from './common/interceptors/transform.interceptor'; import { LoggingInterceptor } from './common/interceptors/logging.interceptor'; import { winstonConfig } from './config/winston.config'; async function bootstrap() { // Create app with custom logger const app = await NestFactory.create<NestExpressApplication>(AppModule, { logger: WinstonModule.createLogger(winstonConfig), cors: true, }); const configService = app.get(ConfigService); const port = configService.get('PORT', 3000); const environment = configService.get('NODE_ENV', 'development'); // Security app.use(helmet()); app.use(compression()); app.use(cookieParser()); // Enable CORS app.enableCors({ origin: configService.get('CORS_ORIGINS', '*').split(','), credentials: true, }); // Global prefix app.setGlobalPrefix('api'); // Versioning app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1', }); // Global pipes app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, transformOptions: { enableImplicitConversion: true, }, }), ); // Global filters app.useGlobalFilters(new HttpExceptionFilter()); // Global interceptors app.useGlobalInterceptors( new TransformInterceptor(), new LoggingInterceptor(), ); // Serve static files app.useStaticAssets(join(__dirname, '..', 'public')); app.setBaseViewsDir(join(__dirname, '..', 'views')); app.setViewEngine('hbs'); // Swagger documentation if (environment !== 'production') { const config = new DocumentBuilder() .setTitle('{{projectName}} API') .setDescription('NestJS API with comprehensive features') .setVersion('1.0') .addBearerAuth() .addTag('auth', 'Authentication endpoints') .addTag('users', 'User management endpoints') .addTag('todos', 'Todo management endpoints') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, document, { swaggerOptions: { persistAuthorization: true, }, }); } // Health check app.get('health', (req, res) => { res.status(200).json({ status: 'ok', timestamp: new Date().toISOString(), environment, uptime: process.uptime(), }); }); // Graceful shutdown app.enableShutdownHooks(); await app.listen(port); console.log(\`๐Ÿš€ Application is running on: http://localhost:\${port}\`); console.log(\`๐Ÿ“š API Documentation: http://localhost:\${port}/api/docs\`); console.log(\`๐Ÿ”ง Environment: \${environment}\`); } bootstrap();`, // App module 'src/app.module.ts': `import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ThrottlerModule } from '@nestjs/throttler'; import { CacheModule } from '@nestjs/cache-manager'; import { BullModule } from '@nestjs/bull'; import { ScheduleModule } from '@nestjs/schedule'; import { TerminusModule } from '@nestjs/terminus'; import { ServeStaticModule } from '@nestjs/serve-static'; import * as redisStore from 'cache-manager-redis-store'; import { join } from 'path'; import { AuthModule } from './auth/auth.module'; import { UsersModule } from './modules/users/users.module'; import { TodosModule } from './modules/todos/todos.module'; import { CommonModule } from './common/common.module'; import { DatabaseModule } from './database/database.module'; import { HealthModule } from './modules/health/health.module'; import { WebSocketModule } from './modules/websocket/websocket.module'; import { EmailModule } from './modules/email/email.module'; import { FileModule } from './modules/file/file.module'; import configuration from './config/configuration'; import { validationSchema } from './config/validation'; @Module({ imports: [ // Configuration ConfigModule.forRoot({ isGlobal: true, load: [configuration], validationSchema, cache: true, }), // Database DatabaseModule, // Rate limiting ThrottlerModule.forRoot([{ ttl: 60000, limit: 100, }]), // Caching CacheModule.registerAsync({ isGlobal: true, imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => ({ store: redisStore as any, host: configService.get('REDIS_HOST'), port: configService.get('REDIS_PORT'), ttl: configService.get('CACHE_TTL', 300), }), }), // Queue BullModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => ({ redis: { host: configService.get('REDIS_HOST'), port: configService.get('REDIS_PORT'), password: configService.get('REDIS_PASSWORD'), }, }), }), // Scheduling ScheduleModule.forRoot(), // Health checks TerminusModule, // Static files ServeStaticModule.forRoot({ rootPath: join(__dirname, '..', 'public'), serveRoot: '/public', }), // Feature modules CommonModule, AuthModule, UsersModule, TodosModule, HealthModule, WebSocketModule, EmailModule, FileModule, ], }) export class AppModule {}`, // Auth module 'src/auth/auth.module.ts': `import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { JwtStrategy } from './strategies/jwt.strategy'; import { LocalStrategy } from './strategies/local.strategy'; import { RefreshTokenStrategy } from './strategies/refresh-token.strategy'; import { User } from '../modules/users/entities/user.entity'; import { UsersModule } from '../modules/users/users.module'; @Module({ imports: [ UsersModule, PassportModule.register({ defaultStrategy: 'jwt' }), JwtModule.registerAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => ({ secret: configService.get('JWT_SECRET'), signOptions: { expiresIn: configService.get('JWT_EXPIRES_IN', '1h'), }, }), }), TypeOrmModule.forFeature([User]), ], controllers: [AuthController], providers: [AuthService, LocalStrategy, JwtStrategy, RefreshTokenStrategy], exports: [AuthService], }) export class AuthModule {}`, // Auth controller 'src/auth/auth.controller.ts': `import { Controller, Post, Body, UseGuards, Request, HttpCode, HttpStatus, Get, Param, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { AuthService } from './auth.service'; import { LocalAuthGuard } from './guards/local-auth.guard'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; import { RefreshTokenGuard } from './guards/refresh-token.guard'; import { Public } from '../common/decorators/public.decorator'; import { CurrentUser } from '../common/decorators/current-user.decorator'; import { RegisterDto } from './dto/register.dto'; import { LoginDto } from './dto/login.dto'; import { RefreshTokenDto } from './dto/refresh-token.dto'; import { ForgotPasswordDto } from './dto/forgot-password.dto'; import { ResetPasswordDto } from './dto/reset-password.dto'; import { ChangePasswordDto } from './dto/change-password.dto'; import { User } from '../modules/users/entities/user.entity'; @ApiTags('auth') @Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {} @Public() @Post('register') @ApiOperation({ summary: 'Register new user' }) @ApiResponse({ status: 201, description: 'User successfully registered' }) @ApiResponse({ status: 400, description: 'Bad request' }) async register(@Body() registerDto: RegisterDto) { return this.authService.register(registerDto); } @Public() @UseGuards(LocalAuthGuard) @Post('login') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'User login' }) @ApiResponse({ status: 200, description: 'Login successful' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) async login(@Request() req, @Body() loginDto: LoginDto) { return this.authService.login(req.user); } @Public() @UseGuards(RefreshTokenGuard) @Post('refresh') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Refresh access token' }) @ApiResponse({ status: 200, description: 'Token refreshed' }) @ApiResponse({ status: 401, description: 'Invalid refresh token' }) async refreshToken(@Request() req, @Body() refreshTokenDto: RefreshTokenDto) { const userId = req.user.sub; const refreshToken = req.user.refreshToken; return this.authService.refreshTokens(userId, refreshToken); } @UseGuards(JwtAuthGuard) @Post('logout') @HttpCode(HttpStatus.OK) @ApiBearerAuth() @ApiOperation({ summary: 'User logout' }) @ApiResponse({ status: 200, description: 'Logout successful' }) async logout(@CurrentUser() user: User) { return this.authService.logout(user.id); } @Public() @Post('forgot-password') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Request password reset' }) @ApiResponse({ status: 200, description: 'Password reset email sent' }) @ApiResponse({ status: 404, description: 'User not found' }) async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto) { return this.authService.forgotPassword(forgotPasswordDto.email); } @Public() @Post('reset-password/:token') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Reset password with token' }) @ApiResponse({ status: 200, description: 'Password reset successful' }) @ApiResponse({ status: 400, description: 'Invalid or expired token' }) async resetPassword( @Param('token') token: string, @Body() resetPasswordDto: ResetPasswordDto, ) { return this.authService.resetPassword(token, resetPasswordDto.password); } @UseGuards(JwtAuthGuard) @Post('change-password') @HttpCode(HttpStatus.OK) @ApiBearerAuth() @ApiOperation({ summary: 'Change user password' }) @ApiResponse({ status: 200, description: 'Password changed successfully' }) @ApiResponse({ status: 400, description: 'Invalid current password' }) async changePassword( @CurrentUser() user: User, @Body() changePasswordDto: ChangePasswordDto, ) { return this.authService.changePassword( user.id, changePasswordDto.currentPassword, changePasswordDto.newPassword, ); } @Public() @Get('verify/:token') @ApiOperation({ summary: 'Verify email address' }) @ApiResponse({ status: 200, description: 'Email verified successfully' }) @ApiResponse({ status: 400, description: 'Invalid or expired token' }) async verifyEmail(@Param('token') token: string) { return this.authService.verifyEmail(token); } @UseGuards(JwtAuthGuard) @Get('me') @ApiBearerAuth() @ApiOperation({ summary: 'Get current user' }) @ApiResponse({ status: 200, description: 'Current user data' }) async getCurrentUser(@CurrentUser() user: User) { return user; } }`, // Auth service 'src/auth/auth.service.ts': `import { Injectable, UnauthorizedException, BadRequestException, NotFoundException, ConflictException, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import * as bcrypt from 'bcryptjs'; import { v4 as uuidv4 } from 'uuid'; import { User } from '../modules/users/entities/user.entity'; import { UsersService } from '../modules/users/users.service'; import { EmailService } from '../modules/email/email.service'; import { RegisterDto } from './dto/register.dto'; @Injectable() export class AuthService { constructor( @InjectRepository(User) private userRepository: Repository<User>, private usersService: UsersService, private jwtService: JwtService, private configService: ConfigService, private emailService: EmailService, ) {} async validateUser(email: string, password: string): Promise<any> { const user = await this.usersService.findByEmail(email); if (user && await bcrypt.compare(password, user.password)) { const { password, ...result } = user; return result; } return null; } async login(user: any) { const tokens = await this.getTokens(user.id, user.email); await this.updateRefreshToken(user.id, tokens.refreshToken); return { user: { id: user.id, email: user.email, name: user.name, role: user.role, }, ...tokens, }; } async register(registerDto: RegisterDto) { // Check if user exists const existingUser = await this.usersService.findByEmail(registerDto.email); if (existingUser) { throw new ConflictException('User with this email already exists'); } // Hash password const hashedPassword = await bcrypt.hash(registerDto.password, 10); // Create verification token const verificationToken = uuidv4(); // Create user const user = await this.userRepository.save({ ...registerDto, password: hashedPassword, verificationToken, isEmailVerified: false, }); // Send verification email await this.emailService.sendVerificationEmail(user.email, verificationToken); // Generate tokens const tokens = await this.getTokens(user.id, user.email); await this.updateRefreshToken(user.id, tokens.refreshToken); return { user: { id: user.id, email: user.email, name: user.name, role: user.role, }, ...tokens, }; } async logout(userId: string) { await this.userRepository.update(userId, { refreshToken: null }); return { message: 'Logout successful' }; } async refreshTokens(userId: string, refreshToken: string) { const user = await this.userRepository.findOne({ where: { id: userId } }); if (!user || !user.refreshToken) { throw new UnauthorizedException('Access denied'); } const refreshTokenMatches = await bcrypt.compare( refreshToken, user.refreshToken, ); if (!refreshTokenMatches) { throw new UnauthorizedException('Access denied'); } const tokens = await this.getTokens(user.id, user.email); await this.updateRefreshToken(user.id, tokens.refreshToken); return tokens; } async forgotPassword(email: string) { const user = await this.usersService.findByEmail(email); if (!user) { throw new NotFoundException('User not found'); } const resetToken = uuidv4(); const resetTokenExpiry = new Date(); resetTokenExpiry.setHours(resetTokenExpiry.getHours() + 1); await this.userRepository.update(user.id, { resetToken, resetTokenExpiry, }); await this.emailService.sendPasswordResetEmail(email, resetToken); return { message: 'Password reset email sent' }; } async resetPassword(token: string, newPassword: string) { const user = await this.userRepository.findOne({ where: { resetToken: token }, }); if (!user || !user.resetTokenExpiry || user.resetTokenExpiry < new Date()) { throw new BadRequestException('Invalid or expired reset token'); } const hashedPassword = await bcrypt.hash(newPassword, 10); await this.userRepository.update(user.id, { password: hashedPassword, resetToken: null, resetTokenExpiry: null, }); return { message: 'Password reset successful' }; } async changePassword(userId: string, currentPassword: string, newPassword: string) { const user = await this.userRepository.findOne({ where: { id: userId } }); const isPasswordValid = await bcrypt.compare(currentPassword, user.password); if (!isPasswordValid) { throw new BadRequestException('Current password is incorrect'); } const hashedPassword = await bcrypt.hash(newPassword, 10); await this.userRepository.update(userId, { password: hashedPassword }); return { message: 'Password changed successfully' }; } async verifyEmail(token: string) { const user = await this.userRepository.findOne({ where: { verificationToken: token }, }); if (!user) { throw new BadRequestException('Invalid verification token'); } await this.userRepository.update(user.id, { isEmailVerified: true, verificationToken: null, }); return { message: 'Email verified successfully' }; } private async getTokens(userId: string, email: string) { const [accessToken, refreshToken] = await Promise.all([ this.jwtService.signAsync( { sub: userId, email, }, { secret: this.configService.get('JWT_SECRET'), expiresIn: this.configService.get('JWT_EXPIRES_IN', '15m'), }, ), this.jwtService.signAsync( { sub: userId, email, }, { secret: this.configService.get('JWT_REFRESH_SECRET'), expiresIn: this.configService.get('JWT_REFRESH_EXPIRES_IN', '7d'), }, ), ]); return { accessToken, refreshToken, }; } private async updateRefreshToken(userId: string, refreshToken: string) { const hashedRefreshToken = await bcrypt.hash(refreshToken, 10); await this.userRepository.update(userId, { refreshToken: hashedRefreshToken, }); } }`, // User entity 'src/modules/users/entities/user.entity.ts': `import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany, BeforeInsert, BeforeUpdate, } from 'typeorm'; import { Exclude } from 'class-transformer'; import { ApiHideProperty } from '@nestjs/swagger'; import { Todo } from '../../todos/entities/todo.entity'; export enum UserRole { USER = 'user', ADMIN = 'admin', } @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') id: string; @Column({ unique: true }) email: string; @Column() name: string; @Column() @Exclude() @ApiHideProperty() password: string; @Column({ type: 'enum', enum: UserRole, default: UserRole.USER, }) role: UserRole; @Column({ default: true }) isActive: boolean; @Column({ default: false }) isEmailVerified: boolean; @Column({ nullable: true }) @Exclude() @ApiHideProperty() refreshToken?: string; @Column({ nullable: true }) @Exclude() @ApiHideProperty() verificationToken?: string; @Column({ nullable: true }) @Exclude() @ApiHideProperty() resetToken?: string; @Column({ nullable: true }) @Exclude() @ApiHideProperty() resetTokenExpiry?: Date; @Column({ nullable: true }) avatar?: string; @Column({ nullable: true }) phone?: string; @Column({ type: 'json', nullable: true }) metadata?: Record<string, any>; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; @OneToMany(() => Todo, (todo) => todo.user) todos: Todo[]; @BeforeInsert() @BeforeUpdate() emailToLowerCase() { this.email = this.email.toLowerCase(); } }`, // Todo entity 'src/modules/todos/entities/todo.entity.ts': `import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn, } from 'typeorm'; import { User } from '../../users/entities/user.entity'; export enum TodoStatus { PENDING = 'pending', IN_PROGRESS = 'in_progress', COMPLETED = 'completed', } export enum TodoPriority { LOW = 'low', MEDIUM = 'medium', HIGH = 'high', } @Entity('todos') export class Todo { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; @Column({ nullable: true }) description?: string; @Column({ type: 'enum', enum: TodoStatus, default: TodoStatus.PENDING, }) status: TodoStatus; @Column({ type: 'enum', enum: TodoPriority, default: TodoPriority.MEDIUM, }) priority: TodoPriority; @Column({ nullable: true }) dueDate?: Date; @Column({ type: 'json', nullable: true }) tags?: string[]; @Column({ default: false }) isArchived: boolean; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; @Column() userId: string; @ManyToOne(() => User, (user) => user.todos, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'userId' }) user: User; }`, // Common module 'src/common/common.module.ts': `import { Module, Global } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { ThrottlerGuard } from '@nestjs/throttler'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; @Global() @Module({ providers: [ { provide: APP_GUARD, useClass: JwtAuthGuard, }, { provide: APP_GUARD, useClass: ThrottlerGuard, }, ], }) export class CommonModule {}`, // Database module 'src/database/database.module.ts': `import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (configService: ConfigService) => ({ type: 'postgres', host: configService.get('DB_HOST'), port: configService.get('DB_PORT'), username: configService.get('DB_USERNAME'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_DATABASE'), entities: [__dirname + '/../**/*.entity{.ts,.js}'], synchronize: configService.get('NODE_ENV') === 'development', logging: configService.get('NODE_ENV') === 'development', migrations: [__dirname + '/migrations/*{.ts,.js}'], migrationsTableName: 'migrations', ssl: configService.get('DB_SSL') === 'true' ? { rejectUnauthorized: false, } : false, }), }), ], }) export class DatabaseModule {}`, // Configuration 'src/config/configuration.ts': `export default () => ({ port: parseInt(process.env.PORT, 10) || 3000, environment: process.env.NODE_ENV || 'development', database: { host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT, 10) || 5432, username: process.env.DB_USERNAME || 'postgres', password: process.env.DB_PASSWORD || 'postgres', database: process.env.DB_DATABASE || '{{projectName}}', ssl: process.env.DB_SSL === 'true', }, jwt: { secret: process.env.JWT_SECRET || 'supersecret', expiresIn: process.env.JWT_EXPIRES_IN || '1h', refreshSecret: process.env.JWT_REFRESH_SECRET || 'refreshsecret', refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d', }, redis: { host: process.env.REDIS_HOST || 'localhost', port: parseInt(process.env.REDIS_PORT, 10) || 6379, password: process.env.REDIS_PASSWORD, }, email: { host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT, 10) || 587, secure: process.env.SMTP_SECURE === 'true', user: process.env.SMTP_USER, pass: process.env.SMTP_PASS, from: process.env.EMAIL_FROM || 'noreply@example.com', }, cors: { origins: process.env.CORS_ORIGINS || 'http://localhost:3000', }, throttle: { ttl: parseInt(process.env.THROTTLE_TTL, 10) || 60, limit: parseInt(process.env.THROTTLE_LIMIT, 10) || 100, }, cache: { ttl: parseInt(process.env.CACHE_TTL, 10) || 300, }, upload: { maxFileSize: parseInt(process.env.MAX_FILE_SIZE, 10) || 10 * 1024 * 1024, uploadDir: process.env.UPLOAD_DIR || './uploads', }, });`, // Environment variables '.env.example': `# Application NODE_ENV=development PORT=3000 # Database DB_HOST=localhost DB_PORT=5432 DB_USERNAME=postgres DB_PASSWORD=postgres DB_DATABASE={{projectName}} DB_SSL=false # JWT JWT_SECRET=your-jwt-secret-key JWT_EXPIRES_IN=1h JWT_REFRESH_SECRET=your-refresh-secret-key JWT_REFRESH_EXPIRES_IN=7d # Redis REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD= # Email SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password EMAIL_FROM=noreply@example.com # CORS CORS_ORIGINS=http://localhost:3000,http://localhost:5173 # Rate Limiting THROTTLE_TTL=60 THROTTLE_LIMIT=100 # Cache CACHE_TTL=300 # File Upload MAX_FILE_SIZE=10485760 UPLOAD_DIR=./uploads`, // Docker configuration 'Dockerfile': `# Build stage FROM node:20-alpine AS builder WORKDIR /app # Copy package files COPY package*.json ./ COPY yarn.lock* ./ # Install dependencies RUN npm ci --only=production && npm cache clean --force RUN npm ci # Copy source code COPY . . # Build application RUN npm run build # Production stage FROM node:20-alpine # Install dumb-init RUN apk add --no-cache dumb-init # Create app user RUN addgroup -g 1001 -S nodejs RUN adduser -S nodejs -u 1001 WORKDIR /app # Copy package files COPY package*.json ./ COPY yarn.lock* ./ # Install production dependencies only RUN npm ci --only=production && npm cache clean --force # Copy built application COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/public ./public COPY --from=builder --chown=nodejs:nodejs /app/views ./views # Create upload directory RUN mkdir -p uploads && chown -R nodejs:nodejs uploads # Switch to non-root user USER nodejs # Expose port EXPOSE 3000 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\ CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1); })" # Start application ENTRYPOINT ["dumb-init", "--"] CMD ["node", "dist/main"]`, 'docker-compose.yml': `version: '3.8' services: app: build: . container_name: {{projectName}}-api ports: - "\${PORT:-3000}:3000" environment: - NODE_ENV=production - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=\${DB_USERNAME:-postgres} - DB_PASSWORD=\${DB_PASSWORD:-postgres} - DB_DATABASE=\${DB_DATABASE:-{{projectName}}} - REDIS_HOST=redis - REDIS_PORT=6379 depends_on: postgres: condition: service_healthy redis: condition: service_healthy volumes: - ./uploads:/app/uploads restart: unless-stopped networks: - app-network postgres: image: postgres:16-alpine container_name: {{projectName}}-db environment: - POSTGRES_USER=\${DB_USERNAME:-postgres} - POSTGRES_PASSWORD=\${DB_PASSWORD:-postgres} - POSTGRES_DB=\${DB_DATABASE:-{{projectName}}} ports: - "\${DB_PORT:-5432}:5432" volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U \${DB_USERNAME:-postgres}"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped networks: - app-network redis: image: redis:7-alpine container_name: {{projectName}}-redis command: redis-server --appendonly yes ports: - "\${REDIS_PORT:-6379}:6379" volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped networks: - app-network nginx: image: nginx:alpine container_name: {{projectName}}-nginx ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro depends_on: - app restart: unless-stopped networks: - app-network volumes: postgres-data: redis-data: networks: app-network: driver: bridge`, // README 'README.md': `# {{projectName}} NestJS API server with modular architecture, comprehensive features, and best practices. ## Features - ๐Ÿ—๏ธ **Modular Architecture** with dependency injection - ๐Ÿ” **JWT Authentication** with refresh tokens - ๐Ÿ—„๏ธ **TypeORM** with PostgreSQL support - ๐Ÿšฆ **Redis** for caching and queues - ๐Ÿ“š **Swagger Documentation** auto-generated - ๐Ÿ”„ **WebSocket** support with Socket.IO - ๐Ÿงช **Testing** with Jest - ๐Ÿณ **Docker** support - ๐Ÿ“Š **Logging** with Winston - ๐Ÿ›ก๏ธ **Security** with Helmet, CORS, rate limiting - ๐Ÿ“ค **File uploads** with validation - โœ‰๏ธ **Email** support with templates - ๐Ÿ”„ **Background jobs** with Bull - ๐Ÿ“… **Task scheduling** with cron - ๐Ÿฅ **Health checks** with Terminus - ๐ŸŽฏ **Validation** with class-validator - ๐Ÿ”„ **Hot reload** in development ## Getting Started ### Prerequisites - Node.js 18+ - PostgreSQL - Redis - Docker (optional) ### Installation 1. Clone the repository 2. Install dependencies: \`\`\`bash npm install \`\`\` 3. Set up environment variables: \`\`\`bash cp .env.example .env \`\`\` 4. Run database migrations: \`\`\`bash npm run migration:run \`\`\` 5. Start the development server: \`\`\`bash npm run start:dev \`\`\` ### Running with Docker \`\`\`bash docker-compose up \`\`\` ## API Documentation Once running, visit: - Swagger UI: http://localhost:3000/api/docs - Health check: http://localhost:3000/health ## Testing \`\`\`bash # Unit tests npm run test # E2E tests npm run test:e2e # Test coverage npm run test:cov \`\`\` ## Project Structure \`\`\` src/ โ”œโ”€โ”€ auth/ # Authentication module โ”œโ”€โ”€ common/ # Common module (guards, filters, etc.) โ”œโ”€โ”€ config/ # Configuration files โ”œโ”€โ”€ database/ # Database module and migrations โ”œโ”€โ”€ modules/ # Feature modules โ”‚ โ”œโ”€โ”€ users/ # Users module โ”‚ โ”œโ”€โ”€ todos/ # Todos module โ”‚ โ”œโ”€โ”€ email/ # Email module โ”‚ โ””โ”€โ”€ ... # Other modules โ”œโ”€โ”€ app.module.ts # Root module โ””โ”€โ”€ main.ts # Application entry \`\`\` ## Commands - \`npm run start\` - Start application - \`npm run start:dev\` - Start in watch mode - \`npm run start:debug\` - Start in debug mode - \`npm run build\` - Build application - \`npm run test\` - Run tests - \`npm run lint\` - Run linter - \`npm run format\` - Format code ## License MIT` } };