[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Skills/NestJS
>

NestJS

NestJS is a TypeScript framework for building server-side Node.js applications with modules, dependency injection, controllers, guards and pipes. Use when creating a NestJS REST API, adding validation, JWT guards, TypeORM or tests, or upgrading a NestJS 11 project to 12.

#typescript#node#api#rest#nestjs
terminal-skillsv1.1.0
Works with:claude-codeopenai-codexgemini-clicursor
Source
Trust Score
93/ 100
2.96×
Impact

Validation

Score is stale (SKILL.md changed since last review)
Quality
93/ 100
Does it follow best practices?
5 PASS · 1 WEAK
Security
Passed
No known issues
Content review + injection scan
Impact
2.96×
27% → 80% agent success
Avg across 3 eval scenarios
Scored 10/24/2026 · skill v1.1.0
$
✓ Installed nestjs v1.1.0

Getting Started

  1. Install the skill using the command above
  2. Open your AI coding agent (Claude Code, Codex, Gemini CLI, or Cursor)
  3. Reference the skill in your prompt
  4. The AI will use the skill's capabilities automatically

Example Prompts

  • "Review the open pull requests and summarize what needs attention"
  • "Generate a changelog from the last 20 commits on the main branch"

Documentation

Overview

NestJS is a modular server framework for Node.js and TypeScript: feature modules, constructor-based dependency injection, decorators for routes and validation, guards for authorization, pipes for input, interceptors and filters for cross-cutting concerns. The current major version is 12 (checked against @nestjs/core 12.2). Its packages are ESM-only, and a new project can be ESM (Vitest tests) or CommonJS (Jest tests). The examples below were compiled against a project generated by the 12 CLI with @nestjs/typeorm 12, typeorm 1.1 and @nestjs/jwt 12.

Instructions

Step 1: Create the project

bash
npm i -g @nestjs/cli
nest new newsdesk-api --package-manager npm
cd newsdesk-api
npm i @nestjs/typeorm typeorm pg @nestjs/jwt @nestjs/config class-validator class-transformer

nest new asks whether the project is CommonJS or ESM (it picks ESM when run without a terminal). Running the CLI generators needs Node.js 22.22.3+, 24.15+ or 26+; running the finished application needs 20.19+ (or 22.12+). The examples use the ESM layout, where relative imports end in .js; in a CommonJS project drop the .js suffix and use jest.fn() instead of vi.fn().

nest g resource articles scaffolds a module, controller, service and DTOs.

Step 2: Entity and DTO

typescript
// src/articles/entities/article.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';

@Entity()
export class Article {
  @PrimaryGeneratedColumn()
  id: number;
  @Column({ length: 200 })
  title: string;
  @Column('text')
  body: string;
  @CreateDateColumn()
  createdAt: Date;
}
typescript
// src/articles/dto/create-article.dto.ts
import { IsString, IsNotEmpty, MaxLength } from 'class-validator';

export class CreateArticleDto {
  @IsString()
  @IsNotEmpty()
  @MaxLength(200)
  title: string;
  @IsString()
  @IsNotEmpty()
  body: string;
}

Note: with ESM, a TypeORM relation between two entity files needs the Relation<User> wrapper type from typeorm on the property to avoid a circular-import error.

Step 3: Service and controller

typescript
// src/articles/articles.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Article } from './entities/article.entity.js';
import { CreateArticleDto } from './dto/create-article.dto.js';

@Injectable()
export class ArticlesService {
  constructor(
    @InjectRepository(Article)
    private readonly repo: Repository<Article>,
  ) {}

  findAll(page: number, limit: number) {
    return this.repo.find({
      skip: (page - 1) * limit,
      take: limit,
      order: { createdAt: 'DESC' },
    });
  }
  async findOne(id: number) {
    const article = await this.repo.findOne({ where: { id } });
    if (!article) throw new NotFoundException(`Article #${id} not found`);
    return article;
  }
  create(dto: CreateArticleDto) {
    return this.repo.save(this.repo.create(dto));
  }
  async remove(id: number) {
    const result = await this.repo.delete(id);
    if (result.affected === 0) throw new NotFoundException(`Article #${id} not found`);
  }
}
typescript
// src/articles/articles.controller.ts
import {
  Controller, Get, Post, Body, Param, Delete,
  ParseIntPipe, DefaultValuePipe, UseGuards, Query, HttpCode, HttpStatus,
} from '@nestjs/common';
import { ArticlesService } from './articles.service.js';
import { CreateArticleDto } from './dto/create-article.dto.js';
import { AuthGuard } from '../auth/auth.guard.js';

@Controller('articles')
export class ArticlesController {
  constructor(private readonly articlesService: ArticlesService) {}

  @Get()
  findAll(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('limit', new DefaultValuePipe(20), ParseIntPipe) limit: number,
  ) {
    return this.articlesService.findAll(page, limit);
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.articlesService.findOne(id);
  }

  @Post()
  @UseGuards(AuthGuard)
  create(@Body() dto: CreateArticleDto) {
    return this.articlesService.create(dto);
  }

  @Delete(':id')
  @UseGuards(AuthGuard)
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.articlesService.remove(id);
  }
}

Step 4: Auth module and guard

The guard needs JwtService, so the module that uses it must import a module that provides it. Put JwtModule in an AuthModule and export it; without that import Nest fails at startup with "can't resolve dependencies of the AuthGuard".

typescript
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { AuthGuard } from './auth.guard.js';

@Module({
  imports: [
    JwtModule.registerAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        secret: config.getOrThrow<string>('JWT_SECRET'),
        signOptions: { expiresIn: '1h' },
      }),
    }),
  ],
  providers: [AuthGuard],
  exports: [JwtModule, AuthGuard],
})
export class AuthModule {}
typescript
// src/auth/auth.guard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import type { Request } from 'express';

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

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest<Request & { user?: unknown }>();
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    if (type !== 'Bearer' || !token) throw new UnauthorizedException();
    try {
      request.user = await this.jwtService.verifyAsync(token);
      return true;
    } catch {
      throw new UnauthorizedException();
    }
  }
}

Step 5: Feature module, root module, bootstrap

typescript
// src/articles/articles.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from '../auth/auth.module.js';
import { ArticlesController } from './articles.controller.js';
import { ArticlesService } from './articles.service.js';
import { Article } from './entities/article.entity.js';

@Module({
  imports: [TypeOrmModule.forFeature([Article]), AuthModule],
  controllers: [ArticlesController],
  providers: [ArticlesService],
  exports: [ArticlesService],
})
export class ArticlesModule {}
typescript
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ArticlesModule } from './articles/articles.module.js';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        type: 'postgres',
        host: config.get('DB_HOST', 'localhost'),
        port: config.get<number>('DB_PORT', 5432),
        database: config.get('DB_NAME', 'newsdesk'),
        username: config.get('DB_USER', 'postgres'),
        password: config.get('DB_PASSWORD'),
        autoLoadEntities: true,
        synchronize: config.get('NODE_ENV') !== 'production',
      }),
    }),
    ArticlesModule,
  ],
})
export class AppModule {}
typescript
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
  app.enableCors({ origin: process.env.CORS_ORIGIN });
  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

Set JWT_SECRET and DB_PASSWORD in .env (never commit it); DB_HOST, DB_NAME, DB_USER, CORS_ORIGIN are optional.

Step 6: Test

Unit-test the service by providing a fake repository: Test.createTestingModule({ providers: [ArticlesService, { provide: getRepositoryToken(Article), useValue: { find: vi.fn().mockResolvedValue([]) } }] }).compile(), then moduleRef.get(ArticlesService). Run npm test (Vitest in ESM projects, Jest in CommonJS ones); npm run build compiles to dist/, npm run start:dev watches.

Examples

Example 1: "Add an articles API with login protection"

Follow Steps 1-5, start Postgres, set JWT_SECRET, run npm run start:dev. Then curl 'http://localhost:3000/articles?page=1&limit=5' returns a JSON array, and curl -X POST http://localhost:3000/articles -H 'Content-Type: application/json' -d '{"title":"Q3 report","body":"Revenue is up"}' answers 401 Unauthorized until a valid Authorization: Bearer <jwt> header is sent; with a valid token, a body with an empty title answers 400 with the validation messages.

Example 2: "Upgrade my NestJS 11 service to 12"

Install @nestjs/cli@latest and @nestjs/schematics@latest globally, run nest upgrade --dry-run to preview, then nest upgrade. It moves all @nestjs/* packages to 12 together, bumps TypeScript to 6 and applies the mechanical changes. A CommonJS app can stay CommonJS. If you keep Jest, run the tests on Node.js 24.9+ or move to Vitest, otherwise the ESM-only packages fail with ERR_REQUIRE_ASYNC_MODULE.

Guidelines

  • One module per feature; import only the modules you need, and export a provider before another module can inject it.
  • Use ValidationPipe with whitelist: true to strip unknown properties.
  • Keep synchronize off in production; use TypeORM migrations.
  • Read secrets through ConfigService.getOrThrow so a missing JWT_SECRET stops startup instead of signing with an empty key.
  • Version 12 adds Standard Schema validation (Zod, Valibot): @Body({ schema }) plus a global StandardSchemaValidationPipe; class-validator remains supported.
  • Keep every @nestjs/* package on the same major version.