Installable skillApache-2.0

NestJS

NestJS is a progressive TypeScript framework for building scalable server-side applications on Node.js. It uses decorators, modules, dependency injection, and an opinionated architecture inspired by Angular.

BY TerminalSkillsUPDATED Oct 30, 2026
INTENDED FOR✳Claude Code>_Codex◈OpenClaw↗Cursor✦GeminiInstall in a listed agent.

instructions.md

---
name: nestjs-265bd37efc68
description: >-
  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.
license: Apache-2.0
compatibility: 'Node.js 22.22.3+ or 24.15+ to scaffold with the CLI (20.19+ or 22.12+ to run an app), npm, yarn or pnpm'
metadata:
  author: terminal-skills
  version: 1.1.0
  category: development
  repository: https://github.com/nestjs/nest
  tags: "typescript, node, api, rest, nestjs"
---

# NestJS

## 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.
COMPATIBILITY & REFERENCES

Platform guidance

Imported from the listed AgentSkill.sh category. Complete supplied package files and notices are retained. Required external tools and credentials are not installed. No bundled code was executed during import. Individual agent task behavior has not been tested.