AgentStack
SKILL verified MIT Self-run

Nestjs

skill-omar-obando-qwen-orchestrator-nestjs · by Omar-Obando

>

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add skill-omar-obando-qwen-orchestrator-nestjs

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution Used

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Nestjs? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

NestJS Development Skill — Enterprise TypeScript Framework

This skill provides comprehensive NestJS development patterns for building scalable, testable, enterprise-grade Node.js applications with modular architecture and decorator-driven design.

Key Principles

  • Modular Architecture: Organize code into feature modules with clear boundaries
  • Dependency Injection: Let the container resolve and inject dependencies
  • Decorator-Driven: Use decorators for metadata-driven behavior (routes, validation, guards)
  • Separation of Concerns: Controllers handle HTTP, Services handle logic, Modules wire it together

Module Structure

Feature Module

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductsController } from './products.controller';
import { ProductsService } from './products.service';
import { Product } from './entities/product.entity';
import { ProductRepository } from './repositories/product.repository';

@Module({
  imports: [TypeOrmModule.forFeature([Product])],
  controllers: [ProductsController],
  providers: [ProductsService, ProductRepository],
  exports: [ProductsService],
})
export class ProductsModule {}

Module Types

| Type | Purpose | Example | | -------------- | ---------------------------- | -------------------------------- | | Feature Module | Group related features | ProductsModule | | Shared Module | Reusable across features | DatabaseModule, LoggerModule | | Global Module | Available everywhere | ConfigModule, AuthModule | | Dynamic Module | Configurable at registration | TypeOrmModule.forRootAsync() |

Shared Module Pattern

@Module({
  providers: [LoggerService, DateService],
  exports: [LoggerService, DateService],
})
export class SharedModule {}

// Used in feature modules
@Module({
  imports: [SharedModule],
  // LoggerService and DateService now available for injection
})
export class OrdersModule {}

Global Module

import { Global, Module } from '@nestjs/common';

@Global()
@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule {}
// ConfigService available everywhere without importing

Controllers

REST Controller

import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Patch,
  Param,
  Body,
  Query,
  HttpCode,
  HttpStatus,
  Res,
} from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { PaginationQueryDto } from '../common/dto/pagination-query.dto';

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

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() dto: CreateProductDto) {
    return this.productsService.create(dto);
  }

  @Get()
  findAll(@Query() query: PaginationQueryDto) {
    return this.productsService.findAll(query);
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.productsService.findOne(id);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() dto: UpdateProductDto) {
    return this.productsService.update(id, dto);
  }

  @Patch(':id')
  partialUpdate(
    @Param('id') id: string,
    @Body() dto: Partial
  ) {
    return this.productsService.partialUpdate(id, dto);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id') id: string) {
    return this.productsService.remove(id);
  }
}

DTOs with Validation

import {
  IsString,
  IsNumber,
  IsOptional,
  IsBoolean,
  IsArray,
  Min,
  Max,
  MaxLength,
  IsUUID,
} from 'class-validator';
import { Type } from 'class-transformer';

export class CreateProductDto {
  @IsString()
  @MaxLength(255)
  name: string;

  @IsString()
  @IsUUID()
  categoryId: string;

  @IsNumber({ maxDecimalPlaces: 2 })
  @Min(0)
  @Max(999999.99)
  @Type(() => Number)
  price: number;

  @IsOptional()
  @IsString()
  @MaxLength(5000)
  description?: string;

  @IsOptional()
  @IsBoolean()
  isActive?: boolean;

  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  tags?: string[];
}

export class PaginationQueryDto {
  @IsOptional()
  @Type(() => Number)
  @IsNumber()
  @Min(1)
  page?: number = 1;

  @IsOptional()
  @Type(() => Number)
  @IsNumber()
  @Min(1)
  @Max(100)
  limit?: number = 20;

  @IsOptional()
  @IsString()
  sort?: string = 'createdAt:DESC';
}

Providers (Services)

Injectable Service

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from './entities/product.entity';
import { CreateProductDto } from './dto/create-product.dto';

@Injectable()
export class ProductsService {
  constructor(
    @InjectRepository(Product)
    private readonly productRepo: Repository
  ) {}

  async create(dto: CreateProductDto): Promise {
    const product = this.productRepo.create(dto);
    return this.productRepo.save(product);
  }

  async findAll(query: PaginationQueryDto): Promise {
    const { page, limit, sort } = query;
    const [field, order] = sort.split(':');

    return this.productRepo.findAndCount({
      skip: (page - 1) * limit,
      take: limit,
      order: { [field]: order === 'ASC' ? 'ASC' : 'DESC' },
      relations: ['category'],
    });
  }

  async findOne(id: string): Promise {
    const product = await this.productRepo.findOne({
      where: { id },
      relations: ['category', 'reviews'],
    });
    if (!product) {
      throw new NotFoundException(`Product ${id} not found`);
    }
    return product;
  }

  async update(id: string, dto: UpdateProductDto): Promise {
    const product = await this.findOne(id);
    Object.assign(product, dto);
    return this.productRepo.save(product);
  }

  async remove(id: string): Promise {
    const result = await this.productRepo.delete(id);
    if (result.affected === 0) {
      throw new NotFoundException(`Product ${id} not found`);
    }
  }
}

Custom and Factory Providers

// Factory provider
{
  provide: 'CONFIG',
  useFactory: (envService: EnvService) => {
    return envService.getConfig();
  },
  inject: [EnvService],
}

// Async factory provider
{
  provide: 'DATABASE_CONNECTION',
  useFactory: async (configService: ConfigService) => {
    const connection = await createConnection({
      host: configService.get('DB_HOST'),
      port: configService.get('DB_PORT'),
    });
    return connection;
  },
  inject: [ConfigService],
}

// Value provider
{
  provide: 'API_VERSION',
  useValue: 'v1',
}

Guards (Authentication & Authorization)

JWT Auth Guard (Global)

import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(private readonly jwtService: JwtService) {}

  async canActivate(context: ExecutionContext): Promise {
    const request = context.switchToHttp().getRequest();
    const token = this.extractToken(request);

    if (!token) {
      throw new UnauthorizedException('Missing authentication token');
    }

    try {
      const payload = await this.jwtService.verifyAsync(token);
      request['user'] = payload;
    } catch {
      throw new UnauthorizedException('Invalid or expired token');
    }

    return true;
  }

  private extractToken(request: Request): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}

Role-Based Guard

import { Reflector } from '@nestjs/core';
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';

export const ROLES_KEY = 'roles';

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

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

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

    if (!requiredRoles) return true;

    const { user } = context.switchToHttp().getRequest();

    const hasRole = requiredRoles.some((role) => user.roles?.includes(role));
    if (!hasRole) {
      throw new ForbiddenException('Insufficient permissions');
    }

    return true;
  }
}

// Register globally
@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AuthModule {}

// Usage in controller
@Roles('admin')
@Delete(':id')
remove(@Param('id') id: string) {
  return this.productsService.remove(id);
}

Pipes (Validation & Transformation)

Global Validation Pipe

// main.ts
import { ValidationPipe } from '@nestjs/common';

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true, // Strip unknown properties
    forbidNonWhitelisted: true, // Throw error on unknown properties
    transform: true, // Auto-transform types (string → number)
    transformOptions: {
      enableImplicitConversion: true,
    },
    disableErrorMessages: false,
  })
);

Custom Pipe

import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform {
  transform(value: string): number {
    const parsed = parseInt(value, 10);
    if (isNaN(parsed)) {
      throw new BadRequestException(`"${value}" is not a valid number`);
    }
    return parsed;
  }
}

Interceptors

Logging Interceptor

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  Logger,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger('HTTP');

  intercept(context: ExecutionContext, next: CallHandler): Observable {
    const request = context.switchToHttp().getRequest();
    const { method, url } = request;
    const startTime = Date.now();

    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - startTime;
        this.logger.log(`${method} ${url} — ${duration}ms`);
      })
    );
  }
}

Transform Interceptor

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable, map } from 'rxjs';

export interface ApiResponse {
  success: true;
  data: T;
  timestamp: string;
}

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

Request Lifecycle

Understanding the order of execution is critical:

Incoming Request
    │
    ├── 1. Middleware (module-level, then global)
    ├── 2. Guard (global, then controller, then route)
    ├── 3. Interceptor (before) — global, then controller, then route
    ├── 4. Pipe (global, then parameter-level)
    ├── 5. Controller Method
    ├── 6. Service (business logic)
    ├── 7. Interceptor (after) — route, then controller, then global
    └── 8. Exception Filter (if any error occurred)

CLI Schematics

Code Generation Commands

nest g module products          # Generate module
nest g controller products      # Generate controller
nest g service products         # Generate service
nest g guard auth               # Generate guard
nest g interceptor logging      # Generate interceptor
nest g pipe validate            # Generate pipe
nest g filter http-exception    # Generate exception filter
nest g resource products        # Generate full CRUD module
nest g decorator roles          # Generate custom decorator
nest g middleware logger        # Generate middleware

Resource Generator (Full CRUD)

nest g resource products
# Creates:
#   products/
#   ├── dto/
#   │   ├── create-product.dto.ts
#   │   └── update-product.dto.ts
#   ├── entities/
#   │   └── product.entity.ts
#   ├── products.module.ts
#   ├── products.controller.ts
#   ├── products.controller.spec.ts
#   ├── products.service.ts
#   └── products.service.spec.ts

Testing

Unit Testing with Jest

import { Test, TestingModule } from '@nestjs/testing';
import { ProductsService } from './products.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Product } from './entities/product.entity';
import { NotFoundException } from '@nestjs/common';

describe('ProductsService', () => {
  let service: ProductsService;
  let mockRepo: any;

  beforeEach(async () => {
    mockRepo = {
      find: jest.fn(),
      findOne: jest.fn(),
      create: jest.fn(),
      save: jest.fn(),
      delete: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ProductsService,
        {
          provide: getRepositoryToken(Product),
          useValue: mockRepo,
        },
      ],
    }).compile();

    service = module.get(ProductsService);
  });

  it('should return a product by id', async () => {
    const product = { id: '1', name: 'Test' };
    mockRepo.findOne.mockResolvedValue(product);

    const result = await service.findOne('1');
    expect(result).toEqual(product);
    expect(mockRepo.findOne).toHaveBeenCalledWith({
      where: { id: '1' },
      relations: ['category', 'reviews'],
    });
  });

  it('should throw NotFoundException for missing product', async () => {
    mockRepo.findOne.mockResolvedValue(null);

    await expect(service.findOne('999')).rejects.toThrow(NotFoundException);
  });
});

E2E Testing

import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { AppModule } from '../app.module';
import { INestApplication, ValidationPipe } from '@nestjs/common';

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

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

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
    await app.init();
  });

  it('POST /products — should create a product', () => {
    return request(app.getHttpServer())
      .post('/products')
      .send({ name: 'Widget', price: 9.99, categoryId: 'cat-1' })
      .expect(201);
  });

  it('POST /products — should reject invalid data', () => {
    return request(app.getHttpServer())
      .post('/products')
      .send({ name: 123, price: 'not-a-number' })
      .expect(400);
  });

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

Anti-Patterns

| Anti-Pattern | Problem | Fix | | --------------------------------------- | ------------------------------ | -------------------------------------------------------- | | God modules (everything in one module) | Unmaintainable, hard to test | Split into focused feature modules | | No DTOs (accepting raw objects) | No validation, security risk | Always use class-validator DTOs | | Services without interfaces | Ha

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.