nestjs-expert
NestJS architecture, modules, DI, guards, interceptors, pipes, MongoDB/Mongoose integration, auth, and production patterns. Use when building NestJS APIs, designing module structure, implementing auth, handling errors, writing DTOs, or debugging NestJS-specific issues.
What this skill does
# NestJS Expert
Production NestJS patterns for TypeScript APIs. Stack: NestJS + MongoDB/Mongoose + TypeScript strict mode.
## Module architecture
Every feature is a self-contained module. No cross-module direct imports — use exported providers.
```
src/
├── app.module.ts # Root — imports feature modules only
├── common/ # Shared guards, pipes, filters, interceptors
│ ├── filters/
│ ├── guards/
│ ├── interceptors/
│ └── pipes/
├── config/ # ConfigModule setup
└── {feature}/
├── {feature}.module.ts
├── {feature}.controller.ts
├── {feature}.service.ts
├── {feature}.repository.ts # optional, wraps Mongoose model
├── dto/
│ ├── create-{feature}.dto.ts
│ └── update-{feature}.dto.ts
├── schemas/
│ └── {feature}.schema.ts
└── {feature}.types.ts
```
## Dependency injection rules
- Inject interfaces, not concrete classes where possible
- Use `@Injectable({ scope: Scope.DEFAULT })` (singleton) unless you need request-scoped
- Circular deps = architectural problem — fix with `forwardRef` only as last resort
- Test with `Test.createTestingModule` — always mock external services
## Controllers
```typescript
@Controller('resources')
@UseGuards(JwtAuthGuard)
@UseInterceptors(ResponseTransformInterceptor)
export class ResourceController {
constructor(private readonly resourceService: ResourceService) {}
@Get()
async findAll(@Query() query: PaginationQueryDto) {
return this.resourceService.findAll(query);
}
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body() dto: CreateResourceDto, @CurrentUser() user: UserDocument) {
return this.resourceService.create(dto, user._id);
}
}
```
Rules:
- Controllers are thin — no business logic, no DB calls
- Always type `@Body()`, `@Query()`, `@Param()` with DTOs
- Use `@CurrentUser()` custom decorator, never `@Req()`
## DTOs + validation
```typescript
import { IsString, IsEnum, IsOptional, MinLength, MaxLength } from 'class-validator';
import { Transform } from 'class-transformer';
export class CreateResourceDto {
@IsString()
@MinLength(1)
@MaxLength(255)
name: string;
@IsEnum(ResourceStatus)
status: ResourceStatus;
@IsOptional()
@IsString()
@Transform(({ value }) => value?.trim())
description?: string;
}
```
Global validation pipe in `main.ts`:
```typescript
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // strip unknown props
forbidNonWhitelisted: true,
transform: true, // auto-transform primitives
transformOptions: { enableImplicitConversion: true },
}));
```
## MongoDB / Mongoose
```typescript
// schema
@Schema({ timestamps: true, versionKey: false })
export class Resource {
@Prop({ required: true, index: true })
name: string;
@Prop({ type: Types.ObjectId, ref: 'User', required: true, index: true })
userId: Types.ObjectId;
@Prop({ enum: ResourceStatus, default: ResourceStatus.ACTIVE })
status: ResourceStatus;
}
export const ResourceSchema = SchemaFactory.createForClass(Resource);
export type ResourceDocument = Resource & Document;
```
```typescript
// service
@Injectable()
export class ResourceService {
constructor(
@InjectModel(Resource.name) private readonly model: Model<ResourceDocument>,
) {}
async findAll(userId: Types.ObjectId, query: PaginationQueryDto) {
const { page = 1, limit = 20 } = query;
return this.model
.find({ userId, deletedAt: null })
.sort({ createdAt: -1 })
.skip((page - 1) * limit)
.limit(limit)
.lean()
.exec();
}
}
```
Rules:
- Always `.lean()` for read queries (plain objects, ~30% faster)
- Always `.exec()` to get a real Promise
- Use `Types.ObjectId` not `string` for references in service layer
- Soft delete: `deletedAt: Date | null`, never hard delete user data
## Auth pattern
```typescript
// JWT strategy
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get<string>('JWT_SECRET'),
ignoreExpiration: false,
});
}
async validate(payload: JwtPayload): Promise<UserDocument> {
// return value is injected as req.user
return { _id: payload.sub, email: payload.email };
}
}
// custom decorator
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => ctx.switchToHttp().getRequest().user,
);
```
## Guards
```typescript
@Injectable()
export class ResourceOwnerGuard implements CanActivate {
constructor(private readonly resourceService: ResourceService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const { user, params } = context.switchToHttp().getRequest();
const resource = await this.resourceService.findById(params.id);
return resource?.userId.equals(user._id) ?? false;
}
}
```
## Exception filter
```typescript
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(GlobalExceptionFilter.name);
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
if (exception instanceof HttpException) {
return response.status(exception.getStatus()).json({
statusCode: exception.getStatus(),
message: exception.message,
});
}
this.logger.error('Unhandled exception', exception instanceof Error ? exception.stack : exception);
return response.status(500).json({ statusCode: 500, message: 'Internal server error' });
}
}
```
## Config
```typescript
// config/app.config.ts
export default registerAs('app', () => ({
port: parseInt(process.env.PORT ?? '3000', 10),
jwtSecret: process.env.JWT_SECRET,
mongoUri: process.env.MONGO_URI,
}));
// access in service
constructor(private config: ConfigService) {}
const port = this.config.get<number>('app.port');
```
Never use `process.env` directly outside config files.
## Performance rules
- `lean()` on all read queries
- Add indexes for every field used in `find()` filter or `sort()`
- Compound indexes for multi-field queries: `{ userId: 1, createdAt: -1 }`
- Use `select()` to project only needed fields on large documents
- Cache with `@nestjs/cache-manager` for expensive reads
## Common mistakes
| Wrong | Right |
|-------|-------|
| Business logic in controller | Move to service |
| `any` type anywhere | Define interface/DTO |
| `console.log` | `new Logger(ClassName.name)` |
| `req.user` directly | `@CurrentUser()` decorator |
| Hard-coding env vars | `ConfigService` |
| `.find()` without `.lean()` on reads | Always `.lean().exec()` |
| `string` for ObjectId refs | `Types.ObjectId` |
## Related skills
- `nestjs-queue-architect` — BullMQ async job patterns
- `nestjs-testing-expert` — Jest unit + integration testing
- `mongodb-migration-expert` — schema migrations
- `error-handling-expert` — global error strategy
Related in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing — use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.