NestJS Beispiele

NestJS Enterprise Node.js Framework-Beispiele einschließlich Module, Controller, Services und Deployment

💻 NestJS Hello World typescript

🟢 simple

Grundkonfiguration der NestJS-Anwendung und Hello World-Implementierung

// NestJS Hello World Examples

// 1. Basic NestJS Application Structure
// src/main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { ValidationPipe } from '@nestjs/common'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)

  // Enable validation globally
  app.useGlobalPipes(new ValidationPipe())

  // Enable CORS
  app.enableCors()

  // Prefix all routes
  app.setGlobalPrefix('api')

  await app.listen(3000)
  console.log('🚀 Application is running on: http://localhost:3000')
}

bootstrap()

// src/app.module.ts
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

// src/app.controller.ts
import { Controller, Get, Param, Query, Post, Body } from '@nestjs/common'
import { AppService } from './app.service'
import { CreateHelloDto } from './dto/create-hello.dto'

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello()
  }

  @Get('hello/:name')
  getHelloName(@Param('name') name: string): string {
    return this.appService.getHelloName(name)
  }

  @Get('greet')
  greet(@Query('name') name: string): object {
    return this.appService.greet(name)
  }

  @Post('hello')
  createHello(@Body() createHelloDto: CreateHelloDto): object {
    return this.appService.createHello(createHelloDto)
  }
}

// src/app.service.ts
import { Injectable } from '@nestjs/common'

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello, World!'
  }

  getHelloName(name: string): string {
    return `Hello, ${name}!`
  }

  greet(name: string): object {
    return {
      message: `Hello, ${name || 'Guest'}!`,
      timestamp: new Date().toISOString(),
      framework: 'NestJS'
    }
  }

  createHello(data: { name: string; message: string }): object {
    return {
      id: Math.random(),
      ...data,
      createdAt: new Date().toISOString()
    }
  }
}

// src/dto/create-hello.dto.ts
import { IsString, IsOptional } from 'class-validator'

export class CreateHelloDto {
  @IsString()
  name: string

  @IsString()
  @IsOptional()
  message?: string
}

// 2. NestJS with Multiple Controllers
// src/users/users.controller.ts
import { Controller, Get, Post, Put, Delete, Param, Body, HttpStatus, HttpException } from '@nestjs/common'
import { UsersService } from './users.service'
import { CreateUserDto, UpdateUserDto } from './dto'

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  async create(@Body() createUserDto: CreateUserDto) {
    try {
      return await this.usersService.create(createUserDto)
    } catch (error) {
      throw new HttpException(error.message, HttpStatus.BAD_REQUEST)
    }
  }

  @Get()
  async findAll() {
    return this.usersService.findAll()
  }

  @Get(':id')
  async findOne(@Param('id') id: string) {
    const user = await this.usersService.findOne(+id)
    if (!user) {
      throw new HttpException('User not found', HttpStatus.NOT_FOUND)
    }
    return user
  }

  @Put(':id')
  async update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    const user = await this.usersService.update(+id, updateUserDto)
    if (!user) {
      throw new HttpException('User not found', HttpStatus.NOT_FOUND)
    }
    return user
  }

  @Delete(':id')
  async remove(@Param('id') id: string) {
    const result = await this.usersService.remove(+id)
    if (!result) {
      throw new HttpException('User not found', HttpStatus.NOT_FOUND)
    }
    return { message: 'User deleted successfully' }
  }
}

// src/users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common'
import { CreateUserDto, UpdateUserDto } from './dto'

export interface User {
  id: number
  name: string
  email: string
  age?: number
  createdAt: Date
  updatedAt: Date
}

@Injectable()
export class UsersService {
  private users: User[] = []
  private nextId = 1

  create(createUserDto: CreateUserDto): User {
    const user: User = {
      id: this.nextId++,
      ...createUserDto,
      createdAt: new Date(),
      updatedAt: new Date()
    }
    this.users.push(user)
    return user
  }

  findAll(): User[] {
    return this.users
  }

  findOne(id: number): User | undefined {
    return this.users.find(user => user.id === id)
  }

  update(id: number, updateUserDto: UpdateUserDto): User | undefined {
    const userIndex = this.users.findIndex(user => user.id === id)
    if (userIndex === -1) {
      return undefined
    }

    this.users[userIndex] = {
      ...this.users[userIndex],
      ...updateUserDto,
      updatedAt: new Date()
    }

    return this.users[userIndex]
  }

  remove(id: number): boolean {
    const userIndex = this.users.findIndex(user => user.id === id)
    if (userIndex === -1) {
      return false
    }

    this.users.splice(userIndex, 1)
    return true
  }
}

// src/users/dto/index.ts
export class CreateUserDto {
  name: string
  email: string
  age?: number
}

export class UpdateUserDto {
  name?: string
  email?: string
  age?: number
}

// 3. NestJS with Modules and Dependencies
// src/users/users.module.ts
import { Module } from '@nestjs/common'
import { UsersController } from './users.controller'
import { UsersService } from './users.service'
import { DatabaseModule } from '../database/database.module'

@Module({
  imports: [DatabaseModule],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// src/database/database.module.ts
import { Module, Global } from '@nestjs/common'
import { DatabaseService } from './database.service'

@Global()
@Module({
  providers: [DatabaseService],
  exports: [DatabaseService],
})
export class DatabaseModule {}

// src/database/database.service.ts
import { Injectable } from '@nestjs/common'

export interface DatabaseConnection {
  connect(): Promise<void>
  disconnect(): Promise<void>
  query(sql: string, params?: any[]): Promise<any>
}

@Injectable()
export class DatabaseService {
  private connection: DatabaseConnection

  constructor() {
    this.connection = new PostgresConnection()
  }

  async connect() {
    await this.connection.connect()
  }

  async disconnect() {
    await this.connection.disconnect()
  }

  async query(sql: string, params?: any[]) {
    return this.connection.query(sql, params)
  }
}

class PostgresConnection implements DatabaseConnection {
  async connect() {
    console.log('Connecting to PostgreSQL...')
  }

  async disconnect() {
    console.log('Disconnecting from PostgreSQL...')
  }

  async query(sql: string, params?: any[]) {
    console.log(`Query: ${sql}`, params)
    return { rows: [] }
  }
}

// 4. NestJS with Custom Decorators
// src/decorators/user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common'

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest()
    const user = request.user

    return data ? user?.[data] : user
  },
)

// src/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common'

export const Roles = (...roles: string[]) => SetMetadata('roles', roles)

// Usage in controllers
// src/admin/admin.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common'
import { RolesGuard } from './guards/roles.guard'
import { Roles } from '../decorators/roles.decorator'
import { User } from '../decorators/user.decorator'

@Controller('admin')
@UseGuards(RolesGuard)
@Roles('admin')
export class AdminController {
  @Get('dashboard')
  getDashboard(@User('id') userId: number) {
    return {
      message: 'Admin dashboard',
      userId,
      data: 'Sensitive admin data'
    }
  }

  @Get('users')
  getUsers() {
    return {
      message: 'All users list',
      users: []
    }
  }
}

// 5. NestJS with Guards and Middleware
// src/guards/auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest()
    const token = request.headers.authorization?.replace('Bearer ', '')

    // Simple token validation
    if (!token || token !== 'valid-token') {
      return false
    }

    // Attach user to request
    request.user = {
      id: 1,
      name: 'John Doe',
      role: 'user'
    }

    return true
  }
}

// src/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'
import { Reflector } from '@nestjs/core'

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler())
    if (!requiredRoles) {
      return true
    }

    const { user } = context.switchToHttp().getRequest()
    return requiredRoles.some(role => user.role === role)
  }
}

// src/middleware/logging.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now()
    const { method, url, ip } = req

    console.log(`[${new Date().toISOString()}] ${method} ${url} - ${ip}`)

    res.on('finish', () => {
      const duration = Date.now() - start
      console.log(`[${new Date().toISOString()}] ${method} ${url} - ${res.statusCode} (${duration}ms)`)
    })

    next()
  }
}

// Apply middleware in app module
// src/app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
import { LoggingMiddleware } from './middleware/logging.middleware'
import { UsersModule } from './users/users.module'
import { AuthModule } from './auth/auth.module'

@Module({
  imports: [UsersModule, AuthModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware)
      .forRoutes('*')
  }
}

// 6. NestJS with Interceptors and Filters
// src/interceptors/transform.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'

export interface Response<T> {
  data: T
  timestamp: string
  success: boolean
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
    return next.handle().pipe(
      map(data => ({
        data,
        timestamp: new Date().toISOString(),
        success: true
      }))
    )
  }
}

// src/interceptors/caching.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'
import { Observable, of } from 'rxjs'
import { tap } from 'rxjs/operators'

@Injectable()
export class CachingInterceptor implements NestInterceptor {
  private cache = new Map<string, any>()

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const key = context.switchToHttp().getRequest().url

    if (this.cache.has(key)) {
      return of(this.cache.get(key))
    }

    return next.handle().pipe(
      tap(data => {
        this.cache.set(key, data)
      })
    )
  }
}

// src/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'
import { Request, Response } from 'express'

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse<Response>()
    const request = ctx.getRequest<Request>()

    const status = exception.getStatus()
    const exceptionResponse = exception.getResponse()

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
        method: request.method,
        message: typeof exceptionResponse === 'string'
          ? exceptionResponse
          : (exceptionResponse as any).message,
        error: exception.constructor.name
      })
  }
}

// Apply globally in main.ts
// src/main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { ValidationPipe, HttpExceptionFilter } from '@nestjs/common'
import { TransformInterceptor } from './interceptors/transform.interceptor'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)

  app.useGlobalPipes(new ValidationPipe())
  app.useGlobalFilters(new HttpExceptionFilter())
  app.useGlobalInterceptors(new TransformInterceptor())

  await app.listen(3000)
}

bootstrap()

// 7. NestJS with WebSocket Support
// src/websocket/websocket.gateway.ts
import {
  WebSocketGateway,
  SubscribeMessage,
  WebSocketServer,
  OnGatewayInit,
  OnGatewayConnection,
  OnGatewayDisconnect,
  ConnectedSocket,
  MessageBody,
} from '@nestjs/websockets'
import { Server } from 'socket.io'

export interface ClientData {
  id: string
  name: string
  room?: string
}

export interface MessageData {
  text: string
  sender: string
  timestamp: string
  room?: string
}

@WebSocketGateway({ cors: { origin: '*' } })
export class WebSocketGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer() server: Server
  private clients: Map<string, ClientData> = new Map()

  afterInit(server: Server) {
    console.log('WebSocket Gateway initialized')
  }

  handleConnection(client: any) {
    console.log(`Client connected: ${client.id}`)
    this.clients.set(client.id, {
      id: client.id,
      name: `User${client.id.substr(-4)}`
    })

    client.emit('connected', {
      message: 'Connected to chat server',
      clientId: client.id
    })
  }

  handleDisconnect(client: any) {
    console.log(`Client disconnected: ${client.id}`)
    const clientData = this.clients.get(client.id)
    this.clients.delete(client.id)

    if (clientData) {
      client.broadcast.emit('userLeft', {
        message: `${clientData.name} left the chat`,
        user: clientData
      })
    }
  }

  @SubscribeMessage('joinRoom')
  handleJoinRoom(
    @MessageBody() data: { room: string },
    @ConnectedSocket() client: any,
  ) {
    const clientData = this.clients.get(client.id)
    if (clientData) {
      clientData.room = data.room
      client.join(data.room)

      client.emit('joinedRoom', {
        room: data.room,
        message: `Joined room: ${data.room}`
      })

      client.to(data.room).emit('userJoinedRoom', {
        message: `${clientData.name} joined the room`,
        user: clientData
      })
    }
  }

  @SubscribeMessage('sendMessage')
  handleMessage(
    @MessageBody() data: { text: string; room?: string },
    @ConnectedSocket() client: any,
  ) {
    const clientData = this.clients.get(client.id)
    if (!clientData) return

    const message: MessageData = {
      text: data.text,
      sender: clientData.name,
      timestamp: new Date().toISOString(),
      room: data.room
    }

    if (data.room) {
      this.server.to(data.room).emit('newMessage', message)
    } else {
      client.broadcast.emit('newMessage', message)
    }

    client.emit('messageSent', message)
  }

  @SubscribeMessage('getUsers')
  handleGetUsers(@ConnectedSocket() client: any) {
    const users = Array.from(this.clients.values())
    client.emit('usersList', users)
  }
}

// src/websocket/websocket.module.ts
import { Module } from '@nestjs/common'
import { WebSocketGateway } from './websocket.gateway'

@Module({
  providers: [WebSocketGateway],
  exports: [WebSocketGateway],
})
export class WebSocketModule {}

// 8. NestJS Configuration and Environment
// src/config/configuration.ts
export default () => ({
  port: parseInt(process.env.PORT, 10) || 3000,
  database: {
    host: process.env.DATABASE_HOST || 'localhost',
    port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
    username: process.env.DATABASE_USERNAME || 'postgres',
    password: process.env.DATABASE_PASSWORD || 'password',
    database: process.env.DATABASE_NAME || 'nestjs_db'
  },
  jwt: {
    secret: process.env.JWT_SECRET || 'default-secret',
    expiresIn: process.env.JWT_EXPIRES_IN || '1h'
  },
  redis: {
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT, 10) || 6379,
    password: process.env.REDIS_PASSWORD || undefined
  }
})

// src/config/config.module.ts
import { Module } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
import configuration from './configuration'

@Module({
  imports: [ConfigModule.forRoot({
    load: [configuration],
    isGlobal: true
  })],
  providers: [ConfigService],
  exports: [ConfigService],
})
export class AppConfigModule {}

// 9. NestJS Testing
// src/users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing'
import { UsersService } from './users.service'
import { CreateUserDto } from './dto'

describe('UsersService', () => {
  let service: UsersService

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [UsersService],
    }).compile()

    service = module.get<UsersService>(UsersService)
  })

  it('should be defined', () => {
    expect(service).toBeDefined()
  })

  describe('create', () => {
    it('should create a user successfully', () => {
      const createUserDto: CreateUserDto = {
        name: 'John Doe',
        email: '[email protected]',
        age: 30
      }

      const user = service.create(createUserDto)

      expect(user).toHaveProperty('id')
      expect(user.name).toBe(createUserDto.name)
      expect(user.email).toBe(createUserDto.email)
      expect(user.age).toBe(createUserDto.age)
      expect(user).toHaveProperty('createdAt')
      expect(user).toHaveProperty('updatedAt')
    })

    it('should auto-increment user ID', () => {
      const user1 = service.create({ name: 'User 1', email: '[email protected]' })
      const user2 = service.create({ name: 'User 2', email: '[email protected]' })

      expect(user2.id).toBe(user1.id + 1)
    })
  })

  describe('findAll', () => {
    it('should return empty array when no users exist', () => {
      const users = service.findAll()
      expect(users).toEqual([])
    })

    it('should return all users', () => {
      const user1 = service.create({ name: 'User 1', email: '[email protected]' })
      const user2 = service.create({ name: 'User 2', email: '[email protected]' })

      const users = service.findAll()
      expect(users).toHaveLength(2)
      expect(users).toContainEqual(user1)
      expect(users).toContainEqual(user2)
    })
  })
})

// src/users/users.controller.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing'
import { INestApplication } from '@nestjs/common'
import * as request from 'supertest'
import { AppModule } from '../app.module'

describe('UsersController (e2e)', () => {
  let app: INestApplication

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()

    app = moduleFixture.createNestApplication()
    await app.init()
  })

  describe('/users (POST)', () => {
    it('should create a new user', () => {
      const createUserDto = {
        name: 'John Doe',
        email: '[email protected]'
      }

      return request(app.getHttpServer())
        .post('/api/users')
        .send(createUserDto)
        .expect(201)
        .expect((res) => {
          expect(res.body.name).toBe(createUserDto.name)
          expect(res.body.email).toBe(createUserDto.email)
          expect(res.body).toHaveProperty('id')
          expect(res.body).toHaveProperty('createdAt')
        })
    })

    it('should return 400 for invalid data', () => {
      const invalidUserDto = {
        name: '',
        email: 'invalid-email'
      }

      return request(app.getHttpServer())
        .post('/api/users')
        .send(invalidUserDto)
        .expect(400)
    })
  })

  describe('/users (GET)', () => {
    it('should return array of users', () => {
      return request(app.getHttpServer())
        .get('/api/users')
        .expect(200)
        .expect((res) => {
          expect(Array.isArray(res.body)).toBe(true)
        })
    })
  })
})

// 10. Package.json Configuration
{
  "name": "nestjs-app",
  "version": "0.0.1",
  "description": "NestJS application example",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "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"
  },
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.0.0",
    "@nestjs/websockets": "^10.0.0",
    "@nestjs/config": "^3.0.0",
    "@nestjs/platform-socket.io": "^10.0.0",
    "class-validator": "^0.14.0",
    "class-transformer": "^0.5.1",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.8.1",
    "socket.io": "^4.7.2"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.0.0",
    "@nestjs/schematics": "^10.0.0",
    "@nestjs/testing": "^10.0.0",
    "@types/express": "^4.17.17",
    "@types/jest": "^29.5.2",
    "@types/node": "^20.3.1",
    "@types/supertest": "^2.0.12",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "eslint": "^8.42.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-plugin-prettier": "^5.0.0",
    "jest": "^29.5.0",
    "prettier": "^3.0.0",
    "source-map-support": "^0.5.21",
    "supertest": "^6.3.3",
    "ts-jest": "^29.1.0",
    "ts-loader": "^9.4.3",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.1.3"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\.spec\.ts$",
    "transform": {
      "^.+\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

💻 NestJS Module und Services typescript

🟡 intermediate

Modul-Architektur und Service-Patterns in NestJS-Anwendungen

// NestJS Modules and Services Examples

// 1. Basic Module Structure
// src/core/core.module.ts
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { LoggerModule } from '@nestjs/logger'
import { DatabaseModule } from './database/database.module'

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env'
    }),
    LoggerModule.forRoot({
      isGlobal: true
    }),
    DatabaseModule
  ],
  exports: [DatabaseModule]
})
export class CoreModule {}

// src/core/database/database.module.ts
import { Module } from '@nestjs/common'
import { DatabaseService } from './database.service'

@Module({
  providers: [DatabaseService],
  exports: [DatabaseService]
})
export class DatabaseModule {}

// src/core/database/database.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'

@Injectable()
export class DatabaseService implements OnModuleInit {
  private connection: any

  constructor(private configService: ConfigService) {}

  async onModuleInit() {
    await this.connect()
  }

  private async connect() {
    console.log('Connecting to database...')
    // Database connection logic here
  }

  async query(sql: string, params?: any[]) {
    // Query execution logic here
    return { rows: [] }
  }

  async close() {
    // Connection cleanup logic here
  }
}

// 2. Feature Module Architecture
// src/users/users.module.ts
import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { UsersController } from './users.controller'
import { UsersService } from './users.service'
import { User } from './entities/user.entity'
import { UserProfileModule } from './user-profile/user-profile.module'

@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
    UserProfileModule
  ],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService]
})
export class UsersModule {}

// src/users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './entities/user.entity'
import { CreateUserDto } from './dto/create-user.dto'
import { UpdateUserDto } from './dto/update-user.dto'
import { UserProfileService } from '../user-profile/user-profile.service'

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    private userProfileService: UserProfileService
  ) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.userRepository.create(createUserDto)
    const savedUser = await this.userRepository.save(user)

    // Create user profile
    await this.userProfileService.create({
      userId: savedUser.id,
      bio: createUserDto.bio || ''
    })

    return savedUser
  }

  async findAll(): Promise<User[]> {
    return this.userRepository.find({
      relations: ['profile']
    })
  }

  async findOne(id: number): Promise<User> {
    const user = await this.userRepository.findOne({
      where: { id },
      relations: ['profile']
    })

    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`)
    }

    return user
  }

  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id)
    Object.assign(user, updateUserDto)
    return this.userRepository.save(user)
  }

  async remove(id: number): Promise<void> {
    const user = await this.findOne(id)
    await this.userRepository.remove(user)
  }

  // Custom query methods
  async findByEmail(email: string): Promise<User | null> {
    return this.userRepository.findOne({
      where: { email },
      relations: ['profile']
    })
  }

  async findActiveUsers(): Promise<User[]> {
    return this.userRepository.find({
      where: { isActive: true },
      relations: ['profile']
    })
  }
}

// 3. Service Abstraction with Interfaces
// src/users/interfaces/user-service.interface.ts
import { User } from '../entities/user.entity'
import { CreateUserDto } from '../dto/create-user.dto'
import { UpdateUserDto } from '../dto/update-user.dto'

export interface IUsersService {
  create(createUserDto: CreateUserDto): Promise<User>
  findAll(): Promise<User[]>
  findOne(id: number): Promise<User>
  update(id: number, updateUserDto: UpdateUserDto): Promise<User>
  remove(id: number): Promise<void>
  findByEmail(email: string): Promise<User | null>
  findActiveUsers(): Promise<User[]>
}

// src/users/users.service.ts (implementing interface)
import { Injectable, NotFoundException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './entities/user.entity'
import { CreateUserDto, UpdateUserDto } from './dto'
import { IUsersService } from './interfaces/user-service.interface'

@Injectable()
export class UsersService implements IUsersService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>
  ) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    // Implementation
  }

  async findAll(): Promise<User[]> {
    return this.userRepository.find()
  }

  async findOne(id: number): Promise<User> {
    const user = await this.userRepository.findOne({ where: { id } })
    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`)
    }
    return user
  }

  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id)
    Object.assign(user, updateUserDto)
    return this.userRepository.save(user)
  }

  async remove(id: number): Promise<void> {
    const user = await this.findOne(id)
    await this.userRepository.remove(user)
  }

  async findByEmail(email: string): Promise<User | null> {
    return this.userRepository.findOne({ where: { email } })
  }

  async findActiveUsers(): Promise<User[]> {
    return this.userRepository.find({ where: { isActive: true } })
  }
}

// 4. Provider Registration and Custom Providers
// src/cache/cache.module.ts
import { Module } from '@nestjs/common'
import { CacheService } from './cache.service'
import { REDIS_PROVIDER } from './cache.constants'

// Custom provider factory
export const redisProvider = {
  provide: REDIS_PROVIDER,
  useFactory: async () => {
    const Redis = require('ioredis')
    const redis = new Redis({
      host: process.env.REDIS_HOST || 'localhost',
      port: parseInt(process.env.REDIS_PORT) || 6379,
      password: process.env.REDIS_PASSWORD
    })

    await redis.ping()
    return redis
  }
}

@Module({
  providers: [
    CacheService,
    redisProvider
  ],
  exports: [CacheService]
})
export class CacheModule {}

// src/cache/cache.service.ts
import { Injectable, Inject } from '@nestjs/common'
import { REDIS_PROVIDER } from './cache.constants'

@Injectable()
export class CacheService {
  constructor(@Inject(REDIS_PROVIDER) private readonly redis: any) {}

  async get(key: string): Promise<string | null> {
    return this.redis.get(key)
  }

  async set(key: string, value: string, ttl?: number): Promise<void> {
    if (ttl) {
      await this.redis.setex(key, ttl, value)
    } else {
      await this.redis.set(key, value)
    }
  }

  async del(key: string): Promise<void> {
    await this.redis.del(key)
  }

  async exists(key: string): Promise<boolean> {
    const result = await this.redis.exists(key)
    return result === 1
  }

  async clear(): Promise<void> {
    await this.redis.flushall()
  }
}

// 5. Async Provider Registration
// src/async-connection/async-connection.module.ts
import { Module } from '@nestjs/common'

export const ASYNC_CONNECTION_PROVIDER = 'ASYNC_CONNECTION_PROVIDER'

export const asyncConnectionProvider = {
  provide: ASYNC_CONNECTION_PROVIDER,
  useFactory: async (configService: ConfigService) => {
    // Simulate async connection setup
    await new Promise(resolve => setTimeout(resolve, 1000))

    return {
      connect: () => console.log('Connected asynchronously'),
      disconnect: () => console.log('Disconnected asynchronously')
    }
  },
  inject: [ConfigService]
}

@Module({
  providers: [asyncConnectionProvider],
  exports: [ASYNC_CONNECTION_PROVIDER]
})
export class AsyncConnectionModule {}

// 6. Circular Dependency Resolution
// src/auth/auth.service.ts
import { Injectable, forwardRef, Inject } from '@nestjs/common'
import { UsersService } from '../users/users.service'

@Injectable()
export class AuthService {
  constructor(
    @Inject(forwardRef(() => UsersService))
    private usersService: UsersService
  ) {}

  async validateUser(email: string, password: string) {
    const user = await this.usersService.findByEmail(email)
    if (!user) {
      return null
    }

    // Password validation logic here
    const isValid = await this.validatePassword(password, user.password)
    return isValid ? user : null
  }

  private async validatePassword(password: string, hashedPassword: string) {
    // Password validation logic
    return password === hashedPassword // Simplified
  }

  async login(user: any) {
    return {
      access_token: 'jwt-token',
      user
    }
  }
}

// src/users/users.service.ts (updated to avoid circular dependency)
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './entities/user.entity'

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>
  ) {}

  async findByEmail(email: string): Promise<User | null> {
    return this.userRepository.findOne({ where: { email } })
  }

  // Other methods...
}

// 7. Service Decorators and Scopes
// src/scoped/scoped.service.ts
import { Injectable, Scope } from '@nestjs/common'

@Injectable({ scope: Scope.REQUEST })
export class ScopedService {
  private requestId: string

  constructor() {
    this.requestId = Math.random().toString(36).substr(2, 9)
  }

  getRequestId(): string {
    return this.requestId
  }

  processData(data: any): any {
    return {
      requestId: this.requestId,
      processed: true,
      data,
      timestamp: new Date().toISOString()
    }
  }
}

// src/singleton/singleton.service.ts
import { Injectable } from '@nestjs/common'

@Injectable()
export class SingletonService {
  private static instanceCount = 0
  private instanceId: number

  constructor() {
    SingletonService.instanceCount++
    this.instanceId = SingletonService.instanceCount
  }

  getInstanceId(): number {
    return this.instanceId
  }

  getTotalInstances(): number {
    return SingletonService.instanceCount
  }
}

// 8. Service with Repository Pattern
// src/users/repositories/user.repository.ts
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from '../entities/user.entity'

export interface IUserRepository {
  create(userData: Partial<User>): Promise<User>
  findById(id: number): Promise<User | null>
  findByEmail(email: string): Promise<User | null>
  findAll(): Promise<User[]>
  update(id: number, userData: Partial<User>): Promise<User>
  delete(id: number): Promise<void>
  findActiveUsers(): Promise<User[]>
}

@Injectable()
export class UserRepository implements IUserRepository {
  constructor(
    @InjectRepository(User)
    private readonly repository: Repository<User>
  ) {}

  async create(userData: Partial<User>): Promise<User> {
    const user = this.repository.create(userData)
    return this.repository.save(user)
  }

  async findById(id: number): Promise<User | null> {
    return this.repository.findOne({ where: { id } })
  }

  async findByEmail(email: string): Promise<User | null> {
    return this.repository.findOne({ where: { email } })
  }

  async findAll(): Promise<User[]> {
    return this.repository.find()
  }

  async update(id: number, userData: Partial<User>): Promise<User> {
    await this.repository.update(id, userData)
    return this.findById(id)
  }

  async delete(id: number): Promise<void> {
    await this.repository.delete(id)
  }

  async findActiveUsers(): Promise<User[]> {
    return this.repository.find({ where: { isActive: true } })
  }
}

// src/users/users.service.ts (using repository)
import { Injectable } from '@nestjs/common'
import { CreateUserDto, UpdateUserDto } from './dto'
import { UserRepository, IUserRepository } from './repositories/user.repository'

@Injectable()
export class UsersService {
  constructor(private readonly userRepository: IUserRepository) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    return this.userRepository.create(createUserDto)
  }

  async findAll(): Promise<User[]> {
    return this.userRepository.findAll()
  }

  async findOne(id: number): Promise<User> {
    const user = await this.userRepository.findById(id)
    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`)
    }
    return user
  }

  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    return this.userRepository.update(id, updateUserDto)
  }

  async remove(id: number): Promise<void> {
    await this.userRepository.delete(id)
  }

  async findByEmail(email: string): Promise<User | null> {
    return this.userRepository.findByEmail(email)
  }

  async findActiveUsers(): Promise<User[]> {
    return this.userRepository.findActiveUsers()
  }
}

// 9. Service with Factory Pattern
// src/factories/notification.factory.ts
import { Injectable } from '@nestjs/common'

export interface NotificationProvider {
  send(message: string, recipient: string): Promise<boolean>
}

@Injectable()
export class EmailNotificationProvider implements NotificationProvider {
  async send(message: string, recipient: string): Promise<boolean> {
    console.log(`Sending email to ${recipient}: ${message}`)
    return true
  }
}

@Injectable()
export class SMSNotificationProvider implements NotificationProvider {
  async send(message: string, recipient: string): Promise<boolean> {
    console.log(`Sending SMS to ${recipient}: ${message}`)
    return true
  }
}

@Injectable()
export class PushNotificationProvider implements NotificationProvider {
  async send(message: string, recipient: string): Promise<boolean> {
    console.log(`Sending push notification to ${recipient}: ${message}`)
    return true
  }
}

export class NotificationFactory {
  static createProvider(type: 'email' | 'sms' | 'push'): NotificationProvider {
    switch (type) {
      case 'email':
        return new EmailNotificationProvider()
      case 'sms':
        return new SMSNotificationProvider()
      case 'push':
        return new PushNotificationProvider()
      default:
        throw new Error(`Unknown notification provider: ${type}`)
    }
  }
}

// src/notification/notification.service.ts
import { Injectable } from '@nestjs/common'
import { NotificationFactory, NotificationProvider } from '../factories/notification.factory'

@Injectable()
export class NotificationService {
  private providers: Map<string, NotificationProvider> = new Map()

  constructor() {
    // Register default providers
    this.providers.set('email', NotificationFactory.createProvider('email'))
    this.providers.set('sms', NotificationFactory.createProvider('sms'))
    this.providers.set('push', NotificationFactory.createProvider('push'))
  }

  async sendNotification(
    type: 'email' | 'sms' | 'push',
    message: string,
    recipient: string
  ): Promise<boolean> {
    const provider = this.providers.get(type)
    if (!provider) {
      throw new Error(`Notification provider '${type}' not found`)
    }

    return provider.send(message, recipient)
  }

  registerProvider(type: string, provider: NotificationProvider): void {
    this.providers.set(type, provider)
  }

  async sendMultiChannelNotification(
    message: string,
    recipient: string,
    channels: string[] = ['email', 'sms']
  ): Promise<boolean[]> {
    const results = await Promise.all(
      channels.map(async (channel) => {
        try {
          return await this.sendNotification(channel as any, message, recipient)
        } catch (error) {
          console.error(`Failed to send ${channel} notification:`, error)
          return false
        }
      })
    )

    return results
  }
}

// 10. Service with Event Pattern
// src/events/events.module.ts
import { Module } from '@nestjs/common'
import { EventEmitterModule } from '@nestjs/event-emitter'
import { UserEventsService } from './services/user-events.service'
import { UserCreatedListener } from './listeners/user-created.listener'

@Module({
  imports: [EventEmitterModule.forRoot()],
  providers: [UserEventsService, UserCreatedListener],
  exports: [UserEventsService]
})
export class EventsModule {}

// src/events/services/user-events.service.ts
import { Injectable } from '@nestjs/common'
import { EventEmitter2 } from '@nestjs/event-emitter'

export interface UserCreatedEvent {
  userId: number
  email: string
  name: string
}

export interface UserDeletedEvent {
  userId: number
  deletedAt: Date
}

@Injectable()
export class UserEventsService {
  constructor(private eventEmitter: EventEmitter2) {}

  emitUserCreated(event: UserCreatedEvent) {
    this.eventEmitter.emit('user.created', event)
  }

  emitUserDeleted(event: UserDeletedEvent) {
    this.eventEmitter.emit('user.deleted', event)
  }

  emitUserUpdated(userId: number, changes: any) {
    this.eventEmitter.emit('user.updated', {
      userId,
      changes,
      updatedAt: new Date()
    })
  }
}

// src/events/listeners/user-created.listener.ts
import { Injectable, Logger } from '@nestjs/common'
import { OnEvent } from '@nestjs/event-emitter'
import { UserCreatedEvent } from '../services/user-events.service'
import { NotificationService } from '../notification/notification.service'
import { EmailService } from '../email/email.service'

@Injectable()
export class UserCreatedListener {
  private readonly logger = new Logger(UserCreatedListener.name)

  constructor(
    private readonly notificationService: NotificationService,
    private readonly emailService: EmailService
  ) {}

  @OnEvent('user.created')
  async handleUserCreatedEvent(event: UserCreatedEvent) {
    this.logger.log(`Handling user created event for user ID: ${event.userId}`)

    // Send welcome email
    await this.emailService.sendWelcomeEmail(event.email, event.name)

    // Send welcome notification
    await this.notificationService.sendNotification(
      'push',
      `Welcome ${event.name}! Your account has been created successfully.`,
      `user_${event.userId}`
    )

    // Log analytics
    await this.logUserCreation(event)
  }

  private async logUserCreation(event: UserCreatedEvent) {
    // Analytics logging logic
    this.logger.log(`User analytics logged for ID: ${event.userId}`)
  }
}

💻 NestJS Controller und DTOs typescript

🟡 intermediate

Controller-Patterns, DTOs, Validierung und Routing in NestJS

// NestJS Controllers and DTOs Examples

// 1. Basic Controller Structure
// src/controllers/app.controller.ts
import { Controller, Get, Post, Put, Delete, Param, Query, Body, HttpStatus, HttpCode } from '@nestjs/common'
import { AppService } from '../services/app.service'
import { CreateDto, UpdateDto, QueryDto } from '../dto'

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getRoot(): string {
    return this.appService.getRoot()
  }

  @Get('health')
  @HttpCode(HttpStatus.OK)
  getHealthCheck() {
    return {
      status: 'OK',
      timestamp: new Date().toISOString(),
      uptime: process.uptime()
    }
  }
}

// 2. RESTful Controller with Full CRUD Operations
// src/products/products.controller.ts
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Patch,
  Param,
  Query,
  Body,
  Headers,
  Ip,
  Session,
  UseGuards,
  UseInterceptors,
  UseFilters,
  HttpStatus,
  NotFoundException,
  BadRequestException
} from '@nestjs/common'
import { ProductsService } from '../services/products.service'
import { CreateProductDto, UpdateProductDto, ProductQueryDto } from '../dto'
import { JwtAuthGuard } from '../guards/jwt-auth.guard'
import { LoggingInterceptor } from '../interceptors/logging.interceptor'
import { HttpExceptionFilter } from '../filters/http-exception.filter'
import { CacheInterceptor } from '@nestjs/cache-manager'
import { CacheKey } from '../decorators/cache-key.decorator'

@Controller('products')
@UseGuards(JwtAuthGuard)
@UseInterceptors(LoggingInterceptor, CacheInterceptor)
@UseFilters(HttpExceptionFilter)
export class ProductsController {
  constructor(private readonly productsService: ProductsService) {}

  @Post()
  async create(@Body() createProductDto: CreateProductDto) {
    try {
      const product = await this.productsService.create(createProductDto)
      return {
        success: true,
        data: product,
        message: 'Product created successfully'
      }
    } catch (error) {
      throw new BadRequestException(error.message)
    }
  }

  @Get()
  @CacheKey('products')
  async findAll(@Query() query: ProductQueryDto) {
    const { page = 1, limit = 10, search, category, sortBy = 'createdAt', sortOrder = 'desc' } = query

    const result = await this.productsService.findAll({
      page: +page,
      limit: +limit,
      search,
      category,
      sortBy,
      sortOrder
    })

    return {
      success: true,
      data: result.products,
      pagination: {
        page: result.page,
        limit: result.limit,
        total: result.total,
        totalPages: Math.ceil(result.total / limit)
      }
    }
  }

  @Get(':id')
  async findOne(
    @Param('id', ParseIntPipe) id: number,
    @Headers('accept-language') acceptLanguage: string,
    @Ip() ip: string
  ) {
    const product = await this.productsService.findOne(id)

    if (!product) {
      throw new NotFoundException(`Product with ID ${id} not found`)
    }

    // Log access
    console.log(`Product ${id} accessed from ${ip} with language: ${acceptLanguage}`)

    return {
      success: true,
      data: product
    }
  }

  @Put(':id')
  async update(
    @Param('id', ParseIntPipe) id: number,
    @Body() updateProductDto: UpdateProductDto
  ) {
    const product = await this.productsService.update(id, updateProductDto)

    if (!product) {
      throw new NotFoundException(`Product with ID ${id} not found`)
    }

    return {
      success: true,
      data: product,
      message: 'Product updated successfully'
    }
  }

  @Patch(':id')
  async partialUpdate(
    @Param('id', ParseIntPipe) id: number,
    @Body() partialUpdateDto: Partial<UpdateProductDto>
  ) {
    const product = await this.productsService.partialUpdate(id, partialUpdateDto)

    if (!product) {
      throw new NotFoundException(`Product with ID ${id} not found`)
    }

    return {
      success: true,
      data: product,
      message: 'Product partially updated successfully'
    }
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async remove(@Param('id', ParseIntPipe) id: number) {
    const result = await this.productsService.remove(id)

    if (!result) {
      throw new NotFoundException(`Product with ID ${id} not found`)
    }
  }

  @Post(':id/like')
  async likeProduct(
    @Param('id', ParseIntPipe) id: number,
    @Session() session: Record<string, any>
  ) {
    const userId = session.userId || 'anonymous'
    const result = await this.productsService.likeProduct(id, userId)

    return {
      success: true,
      data: result,
      message: 'Product liked successfully'
    }
  }

  @Get(':id/reviews')
  async getProductReviews(
    @Param('id', ParseIntPipe) id: number,
    @Query('page', ParseIntPipe) page: number = 1,
    @Query('limit', ParseIntPipe) limit: number = 10
  ) {
    const reviews = await this.productsService.getProductReviews(id, page, limit)

    return {
      success: true,
      data: reviews,
      pagination: {
        page,
        limit,
        total: reviews.length
      }
    }
  }
}

// 3. Controller with Custom Decorators
// src/decorators/user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common'

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest()
    const user = request.user

    return data ? user?.[data] : user
  },
)

export const IpAddress = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest()
    return request.ip || request.headers['x-forwarded-for'] || request.connection.remoteAddress
  },
)

// src/decorators/pagination.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common'

export interface Pagination {
  page: number
  limit: number
  offset: number
}

export const Pagination = createParamDecorator(
  (defaultLimit: number = 10, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest()
    const page = parseInt(request.query.page) || 1
    const limit = parseInt(request.query.limit) || defaultLimit

    return {
      page,
      limit,
      offset: (page - 1) * limit
    }
  },
)

// src/controllers/user.controller.ts
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common'
import { UserService } from '../services/user.service'
import { CreateUserDto, UpdateUserDto } from '../dto'
import { User, IpAddress, Pagination } from '../decorators'

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get('profile')
  getUserProfile(@User() user: any) {
    return {
      success: true,
      data: user
    }
  }

  @Get('activity')
  getUserActivity(@User('id') userId: number, @Pagination(20) pagination: Pagination) {
    return this.userService.getUserActivity(userId, pagination)
  }

  @Post()
  async createUser(
    @Body() createUserDto: CreateUserDto,
    @IpAddress() ip: string
  ) {
    const user = await this.userService.create({
      ...createUserDto,
      registrationIp: ip
    })

    return {
      success: true,
      data: user,
      message: 'User created successfully'
    }
  }
}

// 4. DTOs with Validation
// src/dto/create-product.dto.ts
import {
  IsString,
  IsNumber,
  IsOptional,
  IsPositive,
  IsEmail,
  IsUrl,
  IsArray,
  IsEnum,
  MinLength,
  MaxLength,
  Matches,
  IsDateString
} from 'class-validator'

export enum ProductStatus {
  ACTIVE = 'active',
  INACTIVE = 'inactive',
  DRAFT = 'draft'
}

export class CreateProductDto {
  @IsString()
  @MinLength(3, { message: 'Name must be at least 3 characters long' })
  @MaxLength(100, { message: 'Name must not exceed 100 characters' })
  name: string;

  @IsString()
  @MinLength(10, { message: 'Description must be at least 10 characters long' })
  description: string;

  @IsNumber()
  @IsPositive({ message: 'Price must be a positive number' })
  price: number;

  @IsOptional()
  @IsNumber()
  @IsPositive({ message: 'Discount must be a positive number' })
  discount?: number;

  @IsOptional()
  @IsString()
  @IsEnum(ProductStatus, { message: 'Invalid status value' })
  status?: ProductStatus;

  @IsOptional()
  @IsArray()
  @IsString({ each: true, message: 'Each tag must be a string' })
  tags?: string[];

  @IsOptional()
  @IsUrl({}, { message: 'Image URL must be a valid URL' })
  imageUrl?: string;

  @IsOptional()
  @IsString()
  category?: string;

  @IsOptional()
  @IsNumber()
  @IsPositive({ message: 'Stock must be a positive number' })
  stock?: number;

  @IsOptional()
  @IsString()
  @Matches(/^[a-zA-Z0-9-]+$/, { message: 'SKU can only contain letters, numbers, and hyphens' })
  sku?: string;

  @IsOptional()
  @IsDateString({}, { message: 'Launch date must be a valid ISO date string' })
  launchDate?: string;
}

// src/dto/update-product.dto.ts
import { PartialType } from '@nestjs/swagger'
import { CreateProductDto } from './create-product.dto'

export class UpdateProductDto extends PartialType(CreateProductDto) {}

// src/dto/product-query.dto.ts
import {
  IsOptional,
  IsString,
  IsNumber,
  IsEnum,
  IsPositive,
  Min,
  Max
} from 'class-validator'
import { Transform } from 'class-transformer'
import { ProductStatus } from './create-product.dto'

export class ProductQueryDto {
  @IsOptional()
  @Transform(({ value }) => parseInt(value))
  @IsNumber()
  @IsPositive()
  @Min(1)
  page?: number = 1;

  @IsOptional()
  @Transform(({ value }) => parseInt(value))
  @IsNumber()
  @IsPositive()
  @Max(100)
  limit?: number = 10;

  @IsOptional()
  @IsString()
  search?: string;

  @IsOptional()
  @IsString()
  category?: string;

  @IsOptional()
  @IsEnum(ProductStatus)
  status?: ProductStatus;

  @IsOptional()
  @IsEnum(['createdAt', 'updatedAt', 'price', 'name'])
  sortBy?: string = 'createdAt';

  @IsOptional()
  @IsEnum(['asc', 'desc'])
  sortOrder?: 'asc' | 'desc' = 'desc';

  @IsOptional()
  @Transform(({ value }) => value === 'true')
  includeDeleted?: boolean;

  @IsOptional()
  @Transform(({ value }) => parseFloat(value))
  @IsNumber()
  @IsPositive()
  minPrice?: number;

  @IsOptional()
  @Transform(({ value }) => parseFloat(value))
  @IsNumber()
  @IsPositive()
  maxPrice?: number;
}

// 5. Nested DTOs for Complex Objects
// src/dto/create-order.dto.ts
import {
  IsString,
  IsEmail,
  IsArray,
  IsNumber,
  IsPositive,
  ValidateNested,
  ArrayNotEmpty
} from 'class-validator'
import { Type } from 'class-transformer'

export class OrderItemDto {
  @IsNumber()
  @IsPositive()
  productId: number;

  @IsNumber()
  @IsPositive()
  quantity: number;

  @IsOptional()
  @IsNumber()
  @IsPositive()
  unitPrice?: number;
}

export class ShippingAddressDto {
  @IsString()
  street: string;

  @IsString()
  city: string;

  @IsString()
  state: string;

  @IsString()
  @Matches(/^[0-9]{5}(-[0-9]{4})?$/, { message: 'Invalid postal code format' })
  postalCode: string;

  @IsString()
  country: string;

  @IsOptional()
  @IsString()
  addressLine2?: string;
}

export class CreateOrderDto {
  @IsEmail({}, { message: 'Invalid email format' })
  customerEmail: string;

  @IsString()
  customerName: string;

  @IsArray()
  @ArrayNotEmpty({ message: 'Order must contain at least one item' })
  @ValidateNested({ each: true })
  @Type(() => OrderItemDto)
  items: OrderItemDto[];

  @ValidateNested()
  @Type(() => ShippingAddressDto)
  shippingAddress: ShippingAddressDto;

  @IsOptional()
  @IsString()
  @IsEnum(['standard', 'express', 'overnight'])
  shippingMethod?: string;

  @IsOptional()
  @IsString()
  notes?: string;
}

// 6. DTOs with Class Transformer
// src/dto/user.dto.ts
import {
  IsString,
  IsEmail,
  IsOptional,
  IsBoolean,
  IsDateString,
  IsEnum
} from 'class-validator'
import { Transform, Expose } from 'class-transformer'
import { UserRole } from '../enums/user-role.enum'

export class CreateUserDto {
  @IsString()
  @Transform(({ value }) => value?.trim())
  firstName: string;

  @IsString()
  @Transform(({ value }) => value?.trim())
  lastName: string;

  @IsEmail({}, { message: 'Invalid email format' })
  @Transform(({ value }) => value?.toLowerCase().trim())
  email: string;

  @IsString()
  @MinLength(8, { message: 'Password must be at least 8 characters long' })
  @Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/, {
    message: 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character'
  })
  password: string;

  @IsOptional()
  @IsDateString()
  birthDate?: string;

  @IsOptional()
  @IsEnum(UserRole)
  role?: UserRole = UserRole.USER;

  @IsOptional()
  @IsBoolean()
  @Transform(({ value }) => value === 'true')
  isActive?: boolean = true;

  @IsOptional()
  @IsString()
  @Transform(({ value }) => value?.trim())
  phone?: string;

  @IsOptional()
  @Transform(({ value }) => value?.split(',').map((tag: string) => tag.trim()).filter(Boolean))
  tags?: string[];
}

export class UserResponseDto {
  @Expose()
  id: number;

  @Expose()
  email: string;

  @Expose()
  firstName: string;

  @Expose()
  lastName: string;

  @Expose()
  fullName: string;

  @Expose()
  role: UserRole;

  @Expose()
  isActive: boolean;

  @Expose()
  birthDate: string;

  @Expose()
  phone: string;

  @Expose()
  tags: string[];

  @Expose()
  createdAt: string;

  @Expose()
  updatedAt: string;

  @Expose()
  lastLoginAt?: string;
}

// src/dto/user-search.dto.ts
import { IsOptional, IsString, IsBoolean, IsEnum } from 'class-validator'
import { Transform } from 'class-transformer'

export enum SortField {
  NAME = 'name',
  EMAIL = 'email',
  CREATED_AT = 'createdAt',
  LAST_LOGIN = 'lastLoginAt'
}

export enum SortOrder {
  ASC = 'asc',
  DESC = 'desc'
}

export class UserSearchDto {
  @IsOptional()
  @IsString()
  @Transform(({ value }) => value?.trim())
  search?: string;

  @IsOptional()
  @IsString()
  @Transform(({ value }) => value?.trim())
  email?: string;

  @IsOptional()
  @IsString()
  @Transform(({ value }) => value?.trim())
  name?: string;

  @IsOptional()
  @IsBoolean()
  @Transform(({ value }) => value === 'true')
  isActive?: boolean;

  @IsOptional()
  @IsEnum(SortField)
  sortBy?: SortField = SortField.CREATED_AT;

  @IsOptional()
  @IsEnum(SortOrder)
  sortOrder?: SortOrder = SortOrder.DESC;

  @IsOptional()
  @Transform(({ value }) => parseInt(value))
  @IsNumber()
  page?: number = 1;

  @IsOptional()
  @Transform(({ value }) => parseInt(value))
  @IsNumber()
  limit?: number = 10;

  @IsOptional()
  @Transform(({ value }) => value?.split(',').map((tag: string) => tag.trim()))
  tags?: string[];
}

// 7. Controller with Response Transformation
// src/controllers/api/v1/users.controller.ts
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  Body,
  HttpCode,
  HttpStatus,
  UseGuards,
  UseInterceptors
} from '@nestjs/common'
import { UserService } from '../../../services/user.service'
import { CreateUserDto, UpdateUserDto, UserResponseDto, UserSearchDto } from '../../../dto'
import { JwtAuthGuard } from '../../../guards/jwt-auth.guard'
import { TransformInterceptor } from '../../../interceptors/transform.interceptor'
import { Serialize } from '../../../decorators/serialize.decorator'

@Controller('api/v1/users')
@UseGuards(JwtAuthGuard)
@UseInterceptors(TransformInterceptor)
export class ApiV1UsersController {
  constructor(private readonly userService: UserService) {}

  @Post()
  @Serialize(UserResponseDto)
  async create(@Body() createUserDto: CreateUserDto) {
    const user = await this.userService.create(createUserDto)
    return {
      success: true,
      data: user,
      message: 'User created successfully'
    }
  }

  @Get()
  @Serialize(UserResponseDto)
  async findAll(@Query() query: UserSearchDto) {
    const { users, pagination } = await this.userService.findAll(query)
    return {
      success: true,
      data: users,
      pagination
    }
  }

  @Get(':id')
  @Serialize(UserResponseDto)
  async findOne(@Param('id') id: string) {
    const user = await this.userService.findOne(+id)
    return {
      success: true,
      data: user
    }
  }

  @Put(':id')
  @Serialize(UserResponseDto)
  async update(
    @Param('id') id: string,
    @Body() updateUserDto: UpdateUserDto
  ) {
    const user = await this.userService.update(+id, updateUserDto)
    return {
      success: true,
      data: user,
      message: 'User updated successfully'
    }
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async remove(@Param('id') id: string) {
    await this.userService.remove(+id)
  }
}

// 8. Controller with File Upload
// src/controllers/upload.controller.ts
import {
  Controller,
  Post,
  UseInterceptors,
  UploadedFile,
  UploadedFiles,
  Body,
  HttpCode,
  HttpStatus,
  ParseFilePipe,
  MaxFileSizeValidator,
  FileTypeValidator,
  ParseFilePipeBuilder
} from '@nestjs/common'
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express'
import { UploadService } from '../services/upload.service'
import { CreateUploadDto } from '../dto/upload.dto'

@Controller('upload')
export class UploadController {
  constructor(private readonly uploadService: UploadService) {}

  @Post('single')
  @UseInterceptors(FileInterceptor('file'))
  async uploadSingle(
    @UploadedFile(
      new ParseFilePipe({
        validators: [
          new MaxFileSizeValidator({ maxSize: 5 * 1024 * 1024 }), // 5MB
          new FileTypeValidator({ fileType: /(jpeg|jpg|png|gif)$/i })
        ]
      })
    )
    file: Express.Multer.File,
    @Body() uploadDto: CreateUploadDto
  ) {
    const result = await this.uploadService.uploadFile(file, uploadDto)
    return {
      success: true,
      data: result,
      message: 'File uploaded successfully'
    }
  }

  @Post('multiple')
  @UseInterceptors(FilesInterceptor('files', 10))
  async uploadMultiple(
    @UploadedFiles(
      new ParseFilePipeBuilder()
        .addFileTypeValidator({
          fileType: /(jpeg|jpg|png|gif|pdf|doc|docx)$/i,
        })
        .addMaxSizeValidator({
          maxSize: 10 * 1024 * 1024, // 10MB per file
          maxFileSize: 50 * 1024 * 1024, // 50MB total
        })
        .build({
          errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY
        })
    )
    files: Express.Multer.File[],
    @Body() uploadDto: CreateUploadDto
  ) {
    const results = await this.uploadService.uploadMultipleFiles(files, uploadDto)
    return {
      success: true,
      data: results,
      message: `${files.length} files uploaded successfully`
    }
  }

  @Post('image')
  @UseInterceptors(FileInterceptor('image'))
  async uploadImage(
    @UploadedFile(
      new ParseFilePipe({
        validators: [
          new MaxFileSizeValidator({ maxSize: 2 * 1024 * 1024 }), // 2MB
          new FileTypeValidator({ fileType: /(jpeg|jpg|png|webp)$/i })
        ]
      })
    )
    image: Express.Multer.File
  ) {
    const result = await this.uploadService.processImage(image)
    return {
      success: true,
      data: result,
      message: 'Image uploaded and processed successfully'
    }
  }
}

// 9. Controller with Swagger Documentation
// src/controllers/products.controller.ts
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  Body,
  Query,
  HttpStatus
} from '@nestjs/common'
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiParam,
  ApiQuery,
  ApiBody
} from '@nestjs/swagger'
import { ProductsService } from '../services/products.service'
import { CreateProductDto, UpdateProductDto, ProductQueryDto } from '../dto'
import { Product } from '../entities/product.entity'

@ApiTags('products')
@Controller('products')
export class ProductsController {
  constructor(private readonly productsService: ProductsService) {}

  @Post()
  @ApiOperation({ summary: 'Create a new product' })
  @ApiResponse({ status: 201, description: 'Product created successfully', type: Product })
  @ApiResponse({ status: 400, description: 'Bad request - Invalid input data' })
  @ApiBody({ type: CreateProductDto })
  async create(@Body() createProductDto: CreateProductDto): Promise<Product> {
    return this.productsService.create(createProductDto)
  }

  @Get()
  @ApiOperation({ summary: 'Get all products with pagination and filtering' })
  @ApiResponse({ status: 200, description: 'List of products', type: [Product] })
  @ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number' })
  @ApiQuery({ name: 'limit', required: false, type: Number, description: 'Number of items per page' })
  @ApiQuery({ name: 'search', required: false, type: String, description: 'Search term' })
  @ApiQuery({ name: 'category', required: false, type: String, description: 'Product category' })
  async findAll(@Query() query: ProductQueryDto) {
    return this.productsService.findAll(query)
  }

  @Get(':id')
  @ApiOperation({ summary: 'Get a product by ID' })
  @ApiResponse({ status: 200, description: 'Product found', type: Product })
  @ApiResponse({ status: 404, description: 'Product not found' })
  @ApiParam({ name: 'id', description: 'Product ID' })
  async findOne(@Param('id') id: string): Promise<Product> {
    return this.productsService.findOne(+id)
  }

  @Put(':id')
  @ApiOperation({ summary: 'Update a product by ID' })
  @ApiResponse({ status: 200, description: 'Product updated successfully', type: Product })
  @ApiResponse({ status: 404, description: 'Product not found' })
  @ApiResponse({ status: 400, description: 'Bad request - Invalid input data' })
  @ApiParam({ name: 'id', description: 'Product ID' })
  @ApiBody({ type: UpdateProductDto })
  async update(
    @Param('id') id: string,
    @Body() updateProductDto: UpdateProductDto
  ): Promise<Product> {
    return this.productsService.update(+id, updateProductDto)
  }

  @Delete(':id')
  @ApiOperation({ summary: 'Delete a product by ID' })
  @ApiResponse({ status: 204, description: 'Product deleted successfully' })
  @ApiResponse({ status: 404, description: 'Product not found' })
  @ApiParam({ name: 'id', description: 'Product ID' })
  @HttpCode(HttpStatus.NO_CONTENT)
  async remove(@Param('id') id: string): Promise<void> {
    return this.productsService.remove(+id)
  }
}

💻 NestJS Authentifizierung und Tests typescript

🔴 complex

Authentifizierungs-Strategien, Guards und umfassende Test-Patterns

// NestJS Authentication and Testing Examples

// 1. JWT Authentication 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 { AuthService } from './auth.service'
import { AuthController } from './auth.controller'
import { JwtStrategy } from './strategies/jwt.strategy'
import { LocalStrategy } from './strategies/local.strategy'
import { UsersModule } from '../users/users.module'

@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get<string>('JWT_SECRET'),
        signOptions: {
          expiresIn: configService.get<string>('JWT_EXPIRES_IN', '1h')
        }
      }),
      inject: [ConfigService]
    })
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtStrategy, LocalStrategy],
  exports: [AuthService]
})
export class AuthModule {}

// src/auth/auth.service.ts
import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { UsersService } from '../users/users.service'
import { CreateUserDto, LoginDto } from '../dto'
import * as bcrypt from 'bcrypt'
import { User } from '../entities/user.entity'

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService
  ) {}

  async validateUser(email: string, password: string): Promise<User | null> {
    const user = await this.usersService.findByEmail(email)
    if (!user) {
      throw new UnauthorizedException('Invalid credentials')
    }

    const isPasswordValid = await bcrypt.compare(password, user.password)
    if (!isPasswordValid) {
      throw new UnauthorizedException('Invalid credentials')
    }

    return user
  }

  async login(loginDto: LoginDto) {
    const { email, password } = loginDto
    const user = await this.validateUser(email, password)

    if (!user.isActive) {
      throw new UnauthorizedException('Account is deactivated')
    }

    const payload = {
      sub: user.id,
      email: user.email,
      role: user.role,
      name: user.fullName
    }

    const access_token = this.jwtService.sign(payload)

    // Update last login
    await this.usersService.updateLastLogin(user.id)

    return {
      access_token,
      user: {
        id: user.id,
        email: user.email,
        fullName: user.fullName,
        role: user.role
      }
    }
  }

  async register(createUserDto: CreateUserDto) {
    // Check if user already exists
    const existingUser = await this.usersService.findByEmail(createUserDto.email)
    if (existingUser) {
      throw new BadRequestException('Email already registered')
    }

    // Hash password
    const salt = await bcrypt.genSalt(10)
    const hashedPassword = await bcrypt.hash(createUserDto.password, salt)

    // Create user
    const user = await this.usersService.create({
      ...createUserDto,
      password: hashedPassword
    })

    // Generate JWT token
    const payload = {
      sub: user.id,
      email: user.email,
      role: user.role,
      name: user.fullName
    }

    const access_token = this.jwtService.sign(payload)

    return {
      access_token,
      user: {
        id: user.id,
        email: user.email,
        fullName: user.fullName,
        role: user.role
      }
    }
  }

  async refreshToken(user: any) {
    const payload = {
      sub: user.id,
      email: user.email,
      role: user.role,
      name: user.name
    }

    const access_token = this.jwtService.sign(payload)

    return {
      access_token
    }
  }

  async changePassword(userId: number, currentPassword: string, newPassword: string) {
    const user = await this.usersService.findOne(userId)
    if (!user) {
      throw new UnauthorizedException('User not found')
    }

    const isCurrentPasswordValid = await bcrypt.compare(currentPassword, user.password)
    if (!isCurrentPasswordValid) {
      throw new UnauthorizedException('Current password is incorrect')
    }

    const salt = await bcrypt.genSalt(10)
    const hashedNewPassword = await bcrypt.hash(newPassword, salt)

    await this.usersService.update(userId, { password: hashedNewPassword })

    return { message: 'Password changed successfully' }
  }
}

// src/auth/strategies/jwt.strategy.ts
import { Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { ExtractJwt, Strategy } from 'passport-jwt'
import { ConfigService } from '@nestjs/config'

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get<string>('JWT_SECRET'),
    })
  }

  async validate(payload: any) {
    return {
      id: payload.sub,
      email: payload.email,
      role: payload.role,
      name: payload.name
    }
  }
}

// src/auth/strategies/local.strategy.ts
import { Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { Strategy } from 'passport-local'
import { AuthService } from '../auth.service'

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({
      usernameField: 'email'
    })
  }

  async validate(email: string, password: string): Promise<any> {
    return await this.authService.validateUser(email, password)
  }
}

// 2. Authentication Guards
// src/guards/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

// src/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { ROLES_KEY } from '../decorators/roles.decorator'
import { UserRole } from '../enums/user-role.enum'

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ])

    if (!requiredRoles) {
      return true
    }

    const { user } = context.switchToHttp().getRequest()
    return requiredRoles.some((role) => user.role === role)
  }
}

// src/guards/api-key.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'

@Injectable()
export class ApiKeyGuard implements CanActivate {
  constructor(private configService: ConfigService) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest()
    const apiKey = request.headers['x-api-key'] as string

    const validApiKeys = this.configService.get<string>('API_KEYS')?.split(',') || []

    return validApiKeys.includes(apiKey)
  }
}

// src/guards/throttler.guard.ts
import { Injectable, CanActivate, ExecutionContext, HttpException, HttpStatus } from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { Request } from 'express'

interface RateLimit {
  windowMs: number
  max: number
}

@Injectable()
export class ThrottlerGuard implements CanActivate {
  private requests = new Map<string, { count: number; resetTime: number }>()

  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest<Request>()
    const rateLimit = this.reflector.get<RateLimit>('throttle', context.getHandler()) || {
      windowMs: 60000, // 1 minute
      max: 100 // 100 requests per minute
    }

    const key = this.getRequestKey(request)
    const now = Date.now()
    const record = this.requests.get(key)

    if (!record || now > record.resetTime) {
      this.requests.set(key, { count: 1, resetTime: now + rateLimit.windowMs })
      return true
    }

    if (record.count >= rateLimit.max) {
      throw new HttpException('Too Many Requests', HttpStatus.TOO_MANY_REQUESTS)
    }

    record.count++
    return true
  }

  private getRequestKey(request: Request): string {
    const ip = request.ip || request.headers['x-forwarded-for'] || request.connection.remoteAddress
    return `${ip}:${request.path}`
  }
}

// 3. Custom Decorators
// src/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common'
import { UserRole } from '../enums/user-role.enum'

export const ROLES_KEY = 'roles'
export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles)

// src/decorators/throttle.decorator.ts
import { SetMetadata } from '@nestjs/common'

export const THROTTLE_KEY = 'throttle'
export const Throttle = (windowMs: number, max: number) =>
  SetMetadata(THROTTLE_KEY, { windowMs, max })

// src/decorators/public.decorator.ts
import { SetMetadata } from '@nestjs/common'

export const IS_PUBLIC_KEY = 'isPublic'
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true)

// src/decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common'

export const CurrentUser = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest()
    return request.user
  },
)

// 4. Authentication Controller
// src/auth/auth.controller.ts
import {
  Controller,
  Post,
  UseGuards,
  Request,
  Body,
  HttpCode,
  HttpStatus,
  Get
} from '@nestjs/common'
import { AuthService } from './auth.service'
import { LocalAuthGuard } from './guards/local-auth.guard'
import { JwtAuthGuard } from './guards/jwt-auth.guard'
import { CreateUserDto, LoginDto } from '../dto'
import { CurrentUser } from '../decorators/current-user.decorator'
import { Throttle } from '../decorators/throttle.decorator'

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  @UseGuards(LocalAuthGuard)
  @Throttle(60000, 5) // 5 requests per minute
  @HttpCode(HttpStatus.OK)
  async login(@Request() req, @Body() loginDto: LoginDto) {
    return this.authService.login(loginDto)
  }

  @Post('register')
  @Throttle(300000, 3) // 3 requests per 5 minutes
  async register(@Body() createUserDto: CreateUserDto) {
    return this.authService.register(createUserDto)
  }

  @Post('refresh')
  @UseGuards(JwtAuthGuard)
  async refresh(@CurrentUser() user: any) {
    return this.authService.refreshToken(user)
  }

  @Post('change-password')
  @UseGuards(JwtAuthGuard)
  @Throttle(300000, 3) // 3 requests per 5 minutes
  async changePassword(
    @CurrentUser() user: any,
    @Body() body: { currentPassword: string; newPassword: string }
  ) {
    return this.authService.changePassword(user.id, body.currentPassword, body.newPassword)
  }

  @Get('profile')
  @UseGuards(JwtAuthGuard)
  async getProfile(@CurrentUser() user: any) {
    return {
      success: true,
      data: user
    }
  }
}

// 5. Unit Testing
// src/auth/auth.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing'
import { AuthService } from './auth.service'
import { UsersService } from '../users/users.service'
import { JwtService } from '@nestjs/jwt'
import { UnauthorizedException, BadRequestException } from '@nestjs/common'
import { CreateUserDto, LoginDto } from '../dto'
import { User } from '../entities/user.entity'
import * as bcrypt from 'bcrypt'

jest.mock('bcrypt')

describe('AuthService', () => {
  let service: AuthService
  let usersService: UsersService
  let jwtService: JwtService

  const mockUser: User = {
    id: 1,
    email: '[email protected]',
    fullName: 'Test User',
    password: 'hashedPassword',
    role: 'user',
    isActive: true,
    createdAt: new Date(),
    updatedAt: new Date()
  }

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AuthService,
        {
          provide: UsersService,
          useValue: {
            findByEmail: jest.fn(),
            create: jest.fn(),
            findOne: jest.fn(),
            updateLastLogin: jest.fn(),
            update: jest.fn()
          }
        },
        {
          provide: JwtService,
          useValue: {
            sign: jest.fn()
          }
        }
      ]
    }).compile()

    service = module.get<AuthService>(AuthService)
    usersService = module.get<UsersService>(UsersService)
    jwtService = module.get<JwtService>(JwtService)

    (bcrypt.compare as jest.Mock).mockResolvedValue(true)
    (bcrypt.hash as jest.Mock).mockResolvedValue('hashedPassword')
    (bcrypt.genSalt as jest.Mock).mockResolvedValue('salt')
  })

  describe('validateUser', () => {
    it('should return user when credentials are valid', async () => {
      usersService.findByEmail.mockResolvedValue(mockUser)

      const result = await service.validateUser('[email protected]', 'password')

      expect(result).toEqual(mockUser)
      expect(usersService.findByEmail).toHaveBeenCalledWith('[email protected]')
      expect(bcrypt.compare).toHaveBeenCalledWith('password', 'hashedPassword')
    })

    it('should throw UnauthorizedException when user does not exist', async () => {
      usersService.findByEmail.mockResolvedValue(null)

      await expect(service.validateUser('[email protected]', 'password'))
        .rejects.toThrow(UnauthorizedException)
    })

    it('should throw UnauthorizedException when password is invalid', async () => {
      usersService.findByEmail.mockResolvedValue(mockUser)
      ;(bcrypt.compare as jest.Mock).mockResolvedValue(false)

      await expect(service.validateUser('[email protected]', 'wrongpassword'))
        .rejects.toThrow(UnauthorizedException)
    })
  })

  describe('login', () => {
    it('should return access token and user when credentials are valid', async () => {
      const loginDto: LoginDto = { email: '[email protected]', password: 'password' }
      const expectedPayload = { sub: 1, email: '[email protected]', role: 'user', name: 'Test User' }

      usersService.findByEmail.mockResolvedValue(mockUser)
      jwtService.sign.mockReturnValue('jwt-token')

      const result = await service.login(loginDto)

      expect(result).toEqual({
        access_token: 'jwt-token',
        user: {
          id: 1,
          email: '[email protected]',
          fullName: 'Test User',
          role: 'user'
        }
      })
      expect(jwtService.sign).toHaveBeenCalledWith(expectedPayload)
    })

    it('should throw UnauthorizedException when account is deactivated', async () => {
      const deactivatedUser = { ...mockUser, isActive: false }
      const loginDto: LoginDto = { email: '[email protected]', password: 'password' }

      usersService.findByEmail.mockResolvedValue(deactivatedUser)

      await expect(service.login(loginDto))
        .rejects.toThrow(UnauthorizedException)
    })
  })

  describe('register', () => {
    it('should return access token and user when registration is successful', async () => {
      const createUserDto: CreateUserDto = {
        email: '[email protected]',
        password: 'password123',
        fullName: 'New User'
      }

      usersService.findByEmail.mockResolvedValue(null)
      usersService.create.mockResolvedValue({ id: 2, ...createUserDto })
      jwtService.sign.mockReturnValue('new-jwt-token')

      const result = await service.register(createUserDto)

      expect(result).toEqual({
        access_token: 'new-jwt-token',
        user: {
          id: 2,
          email: '[email protected]',
          fullName: 'New User',
          role: 'user'
        }
      })
    })

    it('should throw BadRequestException when email already exists', async () => {
      const createUserDto: CreateUserDto = {
        email: '[email protected]',
        password: 'password123',
        fullName: 'Existing User'
      }

      usersService.findByEmail.mockResolvedValue(mockUser)

      await expect(service.register(createUserDto))
        .rejects.toThrow(BadRequestException)
    })
  })
})

// 6. E2E Testing
// test/auth.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing'
import { INestApplication } from '@nestjs/common'
import * as request from 'supertest'
import { AppModule } from '../src/app.module'
import { getRepositoryToken } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from '../src/users/entities/user.entity'

describe('Authentication (e2e)', () => {
  let app: INestApplication
  let userRepository: Repository<User>

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()

    app = moduleFixture.createNestApplication()
    userRepository = moduleFixture.get<Repository<User>>(getRepositoryToken(User))

    await app.init()
  })

  afterEach(async () => {
    await userRepository.clear()
    await app.close()
  })

  describe('/auth/register', () => {
    it('should register a new user successfully', () => {
      const createUserDto = {
        email: '[email protected]',
        password: 'password123',
        fullName: 'Test User'
      }

      return request(app.getHttpServer())
        .post('/auth/register')
        .send(createUserDto)
        .expect(201)
        .expect((res) => {
          expect(res.body.access_token).toBeDefined()
          expect(res.body.user.email).toBe(createUserDto.email)
          expect(res.body.user.fullName).toBe(createUserDto.fullName)
        })
    })

    it('should return 400 when email already exists', () => {
      const createUserDto = {
        email: '[email protected]',
        password: 'password123',
        fullName: 'Duplicate User'
      }

      // Create first user
      request(app.getHttpServer())
        .post('/auth/register')
        .send(createUserDto)
        .expect(201)

      // Try to create second user with same email
      return request(app.getHttpServer())
        .post('/auth/register')
        .send(createUserDto)
        .expect(400)
    })
  })

  describe('/auth/login', () => {
    beforeEach(async () => {
      // Create a user for login testing
      const createUserDto = {
        email: '[email protected]',
        password: 'password123',
        fullName: 'Login Test User'
      }

      await request(app.getHttpServer())
        .post('/auth/register')
        .send(createUserDto)
        .expect(201)
    })

    it('should login successfully with valid credentials', () => {
      const loginDto = {
        email: '[email protected]',
        password: 'password123'
      }

      return request(app.getHttpServer())
        .post('/auth/login')
        .send(loginDto)
        .expect(200)
        .expect((res) => {
          expect(res.body.access_token).toBeDefined()
          expect(res.body.user.email).toBe(loginDto.email)
        })
    })

    it('should return 401 with invalid credentials', () => {
      const loginDto = {
        email: '[email protected]',
        password: 'wrongpassword'
      }

      return request(app.getHttpServer())
        .post('/auth/login')
        .send(loginDto)
        .expect(401)
    })
  })

  describe('/auth/profile', () => {
    let accessToken: string

    beforeEach(async () => {
      // Register and login to get access token
      const createUserDto = {
        email: '[email protected]',
        password: 'password123',
        fullName: 'Profile Test User'
      }

      const registerResponse = await request(app.getHttpServer())
        .post('/auth/register')
        .send(createUserDto)

      accessToken = registerResponse.body.access_token
    })

    it('should return user profile with valid token', () => {
      return request(app.getHttpServer())
        .get('/auth/profile')
        .set('Authorization', `Bearer ${accessToken}`)
        .expect(200)
        .expect((res) => {
          expect(res.body.success).toBe(true)
          expect(res.body.data.email).toBe('[email protected]')
        })
    })

    it('should return 401 without token', () => {
      return request(app.getHttpServer())
        .get('/auth/profile')
        .expect(401)
    })

    it('should return 401 with invalid token', () => {
      return request(app.getHttpServer())
        .get('/auth/profile')
        .set('Authorization', 'Bearer invalid-token')
        .expect(401)
    })
  })
})

// 7. Integration Testing with Test Database
// test/auth.integration.spec.ts
import { Test, TestingModule } from '@nestjs/testing'
import { INestApplication } from '@nestjs/common'
import * as request from 'supertest'
import { AppModule } from '../src/app.module'
import { TypeOrmModule } from '@nestjs/typeorm'
import { User } from '../src/users/entities/user.entity'
import { getRepositoryToken } from '@nestjs/typeorm'

describe('Authentication Integration', () => {
  let app: INestApplication
  let userRepository: Repository<User>

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        AppModule,
        TypeOrmModule.forRoot({
          type: 'sqlite',
          database: ':memory:',
          entities: [User],
          synchronize: true,
          logging: false
        })
      ]
    }).compile()

    app = moduleFixture.createNestApplication()
    userRepository = moduleFixture.get<Repository<User>>(getRepositoryToken(User))

    await app.init()
  })

  afterAll(async () => {
    await app.close()
  })

  describe('Complete Authentication Flow', () => {
    it('should handle complete user registration and login flow', async () => {
      const userDto = {
        email: '[email protected]',
        password: 'StrongPassword123!',
        fullName: 'Integration Test User'
      }

      // Step 1: Register user
      const registerResponse = await request(app.getHttpServer())
        .post('/auth/register')
        .send(userDto)
        .expect(201)

      const { access_token, user } = registerResponse.body

      // Step 2: Verify registration response
      expect(access_token).toBeDefined()
      expect(user.email).toBe(userDto.email)
      expect(user.fullName).toBe(userDto.fullName)

      // Step 3: Login with registered credentials
      const loginResponse = await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          email: userDto.email,
          password: userDto.password
        })
        .expect(200)

      // Step 4: Verify login response
      expect(loginResponse.body.access_token).toBeDefined()
      expect(loginResponse.body.user.email).toBe(userDto.email)

      // Step 5: Access protected route
      const profileResponse = await request(app.getHttpServer())
        .get('/auth/profile')
        .set('Authorization', `Bearer ${access_token}`)
        .expect(200)

      expect(profileResponse.body.data.email).toBe(userDto.email)

      // Step 6: Verify user exists in database
      const dbUser = await userRepository.findOne({
        where: { email: userDto.email }
      })

      expect(dbUser).toBeDefined()
      expect(dbUser.fullName).toBe(userDto.fullName)
      expect(dbUser.password).not.toBe(userDto.password) // Password should be hashed
    })

    it('should handle password change', async () => {
      // Register user
      const userDto = {
        email: '[email protected]',
        password: 'OriginalPassword123!',
        fullName: 'Password Test User'
      }

      const registerResponse = await request(app.getHttpServer())
        .post('/auth/register')
        .send(userDto)
        .expect(201)

      const { access_token } = registerResponse.body

      // Change password
      const changePasswordResponse = await request(app.getHttpServer())
        .post('/auth/change-password')
        .set('Authorization', `Bearer ${access_token}`)
        .send({
          currentPassword: 'OriginalPassword123!',
          newPassword: 'NewPassword456!'
        })
        .expect(200)

      // Try login with old password (should fail)
      await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          email: userDto.email,
          password: 'OriginalPassword123!'
        })
        .expect(401)

      // Try login with new password (should succeed)
      const loginResponse = await request(app.getHttpServer())
        .post('/auth/login')
        .send({
          email: userDto.email,
          password: 'NewPassword456!'
        })
        .expect(200)

      expect(loginResponse.body.access_token).toBeDefined()
    })
  })
})

// 8. Mock Testing with Jest Mocks
// src/auth/auth.service.mock.spec.ts
import { Test, TestingModule } from '@nestjs/testing'
import { AuthService } from './auth.service'
import { UsersService } from '../users/users.service'
import { JwtService } from '@nestjs/jwt'

describe('AuthService (Mocked)', () => {
  let service: AuthService
  let mockUsersService: Partial<UsersService>
  let mockJwtService: Partial<JwtService>

  beforeEach(async () => {
    mockUsersService = {
      findByEmail: jest.fn(),
      create: jest.fn(),
      findOne: jest.fn(),
      updateLastLogin: jest.fn(),
      update: jest.fn()
    }

    mockJwtService = {
      sign: jest.fn()
    }

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AuthService,
        {
          provide: UsersService,
          useValue: mockUsersService
        },
        {
          provide: JwtService,
          useValue: mockJwtService
        }
      ]
    }).compile()

    service = module.get<AuthService>(AuthService)
  })

  describe('login', () => {
    it('should handle successful login with mocked dependencies', async () => {
      const mockUser = {
        id: 1,
        email: '[email protected]',
        password: 'hashedPassword',
        isActive: true,
        role: 'user'
      }

      mockUsersService.findByEmail!.mockResolvedValue(mockUser)
      mockJwtService.sign!.mockReturnValue('mock-jwt-token')

      const result = await service.login({
        email: '[email protected]',
        password: 'password'
      })

      expect(mockUsersService.findByEmail).toHaveBeenCalledWith('[email protected]')
      expect(mockJwtService.sign).toHaveBeenCalledWith({
        sub: 1,
        email: '[email protected]',
        role: 'user'
      })
      expect(result).toEqual({
        access_token: 'mock-jwt-token',
        user: {
          id: 1,
          email: '[email protected]',
          fullName: mockUser.fullName,
          role: 'user'
        }
      })
    })
  })
})