📚 Table of Contents

  1. Advanced Decorators
  2. Custom Providers & Factories
  3. Dynamic Modules
  4. Microservices
  5. WebSockets
  6. Testing
  7. Database Integration
  8. Performance Optimization
  9. Security Best Practices

Advanced Decorators

Parameter 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;
  },
);

Method Decorators

// cache.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const CacheTTL = (ttl: number) => SetMetadata('cacheTTL', ttl);

Property Decorators

// transform.decorator.ts
import { Transform } from 'class-transformer';

export function ToLowerCase() {
  return Transform(({ value }) => value?.toLowerCase());
}

// Usage
export class CreateUserDto {
  @ToLowerCase()
  email: string;
}

Composing Decorators

// api-response.decorator.ts
import { applyDecorators, Type } from '@nestjs/common';
import { ApiResponse, ApiOperation } from '@nestjs/swagger';

export function ApiStandardResponse<T>(model: Type<T>) {
  return applyDecorators(
    ApiOperation({ summary: 'Standard response' }),
    ApiResponse({
      status: 200,
      description: 'Success',
      type: model,
    }),
    ApiResponse({
      status: 400,
      description: 'Bad Request',
    }),
  );
}

Custom Providers & Factories

Factory Providers

// database.provider.ts
import { Provider } from '@nestjs/common';

export const DATABASE_CONNECTION = 'DATABASE_CONNECTION';

export const databaseProvider: Provider = {
  provide: DATABASE_CONNECTION,
  useFactory: async () => {
    // Create database connection
    const connection = await createConnection({
      // config
    });
    return connection;
  },
};

Async Providers

// config.module.ts
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({
  providers: [
    {
      provide: ConfigService,
      useFactory: async () => {
        const config = await loadConfig();
        return new ConfigService(config);
      },
    },
  ],
  exports: [ConfigService],
})
export class ConfigModule {}

Value Providers

const constants = {
  API_VERSION: 'v1',
  MAX_RETRIES: 3,
};

@Module({
  providers: [
    {
      provide: 'CONSTANTS',
      useValue: constants,
    },
  ],
})
export class AppModule {}

Class Providers

@Module({
  providers: [
    {
      provide: 'CONFIG_SERVICE',
      useClass: process.env.NODE_ENV === 'production'
        ? ProductionConfigService
        : DevelopmentConfigService,
    },
  ],
})
export class AppModule {}

Dynamic Modules

Creating Dynamic Modules

// cache.module.ts
import { Module, DynamicModule } from '@nestjs/common';

export interface CacheModuleOptions {
  host: string;
  port: number;
  ttl?: number;
}

@Module({})
export class CacheModule {
  static forRoot(options: CacheModuleOptions): DynamicModule {
    return {
      module: CacheModule,
      providers: [
        {
          provide: 'CACHE_OPTIONS',
          useValue: options,
        },
        CacheService,
      ],
      exports: [CacheService],
      global: true,
    };
  }

  static forRootAsync(options: {
    imports?: any[];
    useFactory: (...args: any[]) => Promise<CacheModuleOptions> | CacheModuleOptions;
    inject?: any[];
  }): DynamicModule {
    return {
      module: CacheModule,
      imports: options.imports || [],
      providers: [
        {
          provide: 'CACHE_OPTIONS',
          useFactory: options.useFactory,
          inject: options.inject || [],
        },
        CacheService,
      ],
      exports: [CacheService],
      global: true,
    };
  }
}

Using Dynamic Modules

// Synchronous
CacheModule.forRoot({
  host: 'localhost',
  port: 6379,
  ttl: 3600,
});

// Asynchronous
CacheModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    host: configService.get('REDIS_HOST'),
    port: configService.get('REDIS_PORT'),
  }),
  inject: [ConfigService],
});

Microservices

Setting Up Microservices

npm install @nestjs/microservices

Creating a Microservice

// main.ts (microservice)
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.TCP,
      options: {
        host: 'localhost',
        port: 3001,
      },
    },
  );

  await app.listen();
}
bootstrap();

Message Patterns

// users.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';

@Controller()
export class UsersController {
  @MessagePattern({ cmd: 'get_user' })
  getUser(@Payload() data: { id: string }) {
    return { id: data.id, name: 'John Doe' };
  }

  @MessagePattern({ cmd: 'create_user' })
  createUser(@Payload() data: any) {
    return { id: Date.now(), ...data };
  }
}

Client-Side

// app.module.ts
import { ClientsModule, Transport } from '@nestjs/microservices';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'USER_SERVICE',
        transport: Transport.TCP,
        options: {
          host: 'localhost',
          port: 3001,
        },
      },
    ]),
  ],
})
export class AppModule {}
// app.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';

@Controller()
export class AppController {
  constructor(
    @Inject('USER_SERVICE') private client: ClientProxy,
  ) {}

  @Get()
  async getUsers() {
    return this.client.send({ cmd: 'get_user' }, { id: '1' });
  }
}

RabbitMQ Transport

// RabbitMQ microservice
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
  AppModule,
  {
    transport: Transport.RMQ,
    options: {
      urls: ['amqp://localhost:5672'],
      queue: 'users_queue',
      queueOptions: {
        durable: false,
      },
    },
  },
);

WebSockets

Setting Up WebSockets

npm install @nestjs/websockets @nestjs/platform-socket.io socket.io

Gateway

// chat.gateway.ts
import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  OnGatewayConnection,
  OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway({
  cors: {
    origin: '*',
  },
})
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server: Server;

  handleConnection(client: Socket) {
    console.log(`Client connected: ${client.id}`);
  }

  handleDisconnect(client: Socket) {
    console.log(`Client disconnected: ${client.id}`);
  }

  @SubscribeMessage('message')
  handleMessage(client: Socket, payload: any) {
    // Broadcast to all clients
    this.server.emit('message', {
      id: client.id,
      ...payload,
    });
  }

  @SubscribeMessage('join_room')
  handleJoinRoom(client: Socket, room: string) {
    client.join(room);
    this.server.to(room).emit('user_joined', client.id);
  }
}

Using WebSockets in Frontend

// Frontend example
const socket = io('http://localhost:3000');

socket.on('connect', () => {
  console.log('Connected to server');
});

socket.emit('message', { text: 'Hello!' });

socket.on('message', (data) => {
  console.log('Received:', data);
});

Testing

Unit Testing

// users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';

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();
  });

  it('should create a user', () => {
    const user = service.create({ name: 'John', email: 'john@example.com' });
    expect(user).toHaveProperty('id');
    expect(user.name).toBe('John');
  });
});

E2E Testing

// users.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';

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

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

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

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

  it('/users (POST)', () => {
    return request(app.getHttpServer())
      .post('/users')
      .send({ name: 'John', email: 'john@example.com' })
      .expect(201)
      .expect((res) => {
        expect(res.body).toHaveProperty('id');
      });
  });
});

Mocking

// users.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

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

  const mockUsersService = {
    findAll: jest.fn(() => []),
    findOne: jest.fn((id) => ({ id, name: 'John' })),
    create: jest.fn((dto) => ({ id: '1', ...dto })),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [UsersController],
      providers: [
        {
          provide: UsersService,
          useValue: mockUsersService,
        },
      ],
    }).compile();

    controller = module.get<UsersController>(UsersController);
    service = module.get<UsersService>(UsersService);
  });

  it('should return all users', () => {
    expect(controller.findAll()).toEqual([]);
    expect(mockUsersService.findAll).toHaveBeenCalled();
  });
});

Database Integration

TypeORM Setup

npm install @nestjs/typeorm typeorm mysql2
// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'test',
      entities: [User],
      synchronize: true, // Only for development
    }),
    UsersModule,
  ],
})
export class AppModule {}

Entity

// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column({ unique: true })
  email: string;

  @Column({ default: true })
  isActive: boolean;
}

Repository Pattern

// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

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

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

  findOne(id: number): Promise<User> {
    return this.usersRepository.findOne({ where: { id } });
  }

  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.usersRepository.create(createUserDto);
    return this.usersRepository.save(user);
  }

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

Prisma Setup

npm install prisma @prisma/client
npx prisma init
// prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}

Performance Optimization

Caching

// app.module.ts
import { CacheModule } from '@nestjs/cache-manager';
import { redisStore } from 'cache-manager-redis-store';

@Module({
  imports: [
    CacheModule.register({
      store: redisStore,
      host: 'localhost',
      port: 6379,
      ttl: 300,
    }),
  ],
})
export class AppModule {}
// users.controller.ts
import { CacheKey, CacheTTL } from '@nestjs/cache-manager';
import { UseInterceptors, CacheInterceptor } from '@nestjs/common';

@Controller('users')
@UseInterceptors(CacheInterceptor)
export class UsersController {
  @Get()
  @CacheKey('users')
  @CacheTTL(60)
  findAll() {
    return this.usersService.findAll();
  }
}

Compression

npm install compression
// main.ts
import * as compression from 'compression';

app.use(compression());

Rate Limiting

npm install @nestjs/throttler
// app.module.ts
import { ThrottlerModule } from '@nestjs/throttler';

@Module({
  imports: [
    ThrottlerModule.forRoot({
      ttl: 60,
      limit: 10,
    }),
  ],
})
export class AppModule {}

Security Best Practices

Helmet

npm install helmet
// main.ts
import helmet from 'helmet';

app.use(helmet());

CSRF Protection

npm install csurf
// main.ts
import * as csurf from 'csurf';

app.use(csurf());

Input Sanitization

// sanitize.pipe.ts
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';

@Injectable()
export class SanitizePipe implements PipeTransform {
  transform(value: any) {
    if (typeof value === 'string') {
      return value.trim().replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
    }
    return value;
  }
}

Password Hashing

npm install bcrypt
npm install --save-dev @types/bcrypt
import * as bcrypt from 'bcrypt';

async hashPassword(password: string): Promise<string> {
  const salt = await bcrypt.genSalt(10);
  return bcrypt.hash(password, salt);
}

async comparePassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

Exercises

  1. Create a dynamic module for email service configuration
  2. Implement a microservice for handling notifications
  3. Build a WebSocket gateway for real-time chat
  4. Write comprehensive unit and E2E tests
  5. Integrate a database and implement full CRUD operations
  6. Add caching and rate limiting to your API

Next Steps

Now that you've mastered advanced concepts, practice by: - Building a complete full-stack application - Implementing authentication and authorization - Deploying to production - Monitoring and logging