@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
JavaScript
"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`
}
};