💻 Développement

dev-nestjs-guide

Développement backend NestJS avec modules, controllers, providers, pipes, guards et interceptors.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-nestjs-guide --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-nestjs-guide -Launch"

🚀 Déjà installé ?

claude "/dev-nestjs-guide"

Ou tapez /dev-nestjs-guide dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

NestJSNest.jsmodule NestJScontroller NestJSdecorator NestJS

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-nestjs-guide ~/.claude/skills/

Payload du plugin : skills/dev-nestjs-guide · source éditable : dev-skills/nestjs-guide

📖 Manuel

Guide NestJS

1. Bootstrap du projet

npm i -g @nestjs/cli
nest new my-api --strict          # TypeScript strict activé d'emblée
cd my-api
nest g resource users             # CRUD complet : module + controller + service + dto + entity

Choix d'ORM :

CasORM recommandé
Relations complexes, legacy DBTypeORM
DX moderne, migrations autoPrisma
Multi-DB, unit-of-work strictMikroORM

2. Structure modulaire

src/
  app.module.ts          ← racine
  core/                  ← CoreModule (logger, config, DB)
  shared/                ← SharedModule (guards, pipes, interceptors réutilisables)
  users/
    users.module.ts
    users.controller.ts
    users.service.ts
    dto/
      create-user.dto.ts
      update-user.dto.ts
    entities/
      user.entity.ts

Règle : un feature module déclare et exporte ce dont les autres ont besoin. Ne rien exporter par défaut.

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],   // seulement si d'autres modules en ont besoin
})
export class UsersModule {}

3. Controllers et DTOs

@ApiTags('users')
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

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

  @Get(':id')
  findOne(@Param('id', ParseUUIDPipe) id: string) {
    return this.usersService.findOneOrFail(id);
  }
}

DTO avec validation :

import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsString()
  @MinLength(8)
  password: string;
}

Active le pipe global dans main.ts :

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,          // strip propriétés non décorées
    forbidNonWhitelisted: true,
    transform: true,          // cast automatique (string → number, etc.)
  }),
);

4. Services et injection de dépendances

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly repo: Repository<User>,
    private readonly configService: ConfigService,
  ) {}

  async findOneOrFail(id: string): Promise<User> {
    const user = await this.repo.findOne({ where: { id } });
    if (!user) throw new NotFoundException(`User ${id} not found`);
    return user;
  }
}

Custom provider (ex : client externe) :

{
  provide: 'PAYMENT_CLIENT',
  useFactory: (cfg: ConfigService) =>
    new PaymentClient(cfg.get('PAYMENT_API_KEY')),
  inject: [ConfigService],
}

5. Guards et authentification JWT

// auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

// roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(ctx: ExecutionContext): boolean {
    const roles = this.reflector.getAllAndOverride<string[]>('roles', [
      ctx.getHandler(),
      ctx.getClass(),
    ]);
    if (!roles) return true;
    const { user } = ctx.switchToHttp().getRequest();
    return roles.some((r) => user.roles?.includes(r));
  }
}

// usage
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Delete(':id')
remove(@Param('id', ParseUUIDPipe) id: string) { ... }

Config Passport JWT dans JwtStrategy :

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(cfg: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: cfg.get('JWT_SECRET'),
    });
  }
  validate(payload: JwtPayload) { return payload; }
}

6. Interceptors et gestion des erreurs

Interceptor de logging/transform :

@Injectable()
export class TransformInterceptor<T>
  implements NestInterceptor<T, { data: T; timestamp: string }>
{
  intercept(ctx: ExecutionContext, next: CallHandler) {
    return next.handle().pipe(
      map((data) => ({ data, timestamp: new Date().toISOString() })),
    );
  }
}

Exception filter global :

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;
    ctx.getResponse().status(status).json({
      statusCode: status,
      message: exception instanceof HttpException
        ? exception.message
        : 'Internal server error',
      timestamp: new Date().toISOString(),
    });
  }
}

Enregistrement dans main.ts :

app.useGlobalFilters(new AllExceptionsFilter());
app.useGlobalInterceptors(new TransformInterceptor());

7. Configuration et variables d'environnement

// app.module.ts
ConfigModule.forRoot({
  isGlobal: true,
  validationSchema: Joi.object({
    NODE_ENV: Joi.string().valid('development', 'production', 'test').required(),
    DATABASE_URL: Joi.string().required(),
    JWT_SECRET: Joi.string().min(32).required(),
  }),
}),

8. Tests

// users.service.spec.ts
describe('UsersService', () => {
  let service: UsersService;
  let repo: jest.Mocked<Repository<User>>;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: getRepositoryToken(User), useValue: createMock<Repository<User>>() },
      ],
    }).compile();
    service = module.get(UsersService);
    repo = module.get(getRepositoryToken(User));
  });

  it('throws NotFoundException when user not found', async () => {
    repo.findOne.mockResolvedValue(null);
    await expect(service.findOneOrFail('abc')).rejects.toThrow(NotFoundException);
  });
});

Test e2e :

npm run test:e2e   # utilise supertest + app.init()

9. Anti-patterns et pièges

Anti-patternProblèmeCorrection
Logique métier dans le controllerCouplage HTTP/métierDéplacer dans le service
any sur les corps de requêteBypass de validationDTO + ValidationPipe obligatoire
Module circulaireErreur runtimeforwardRef(() => ModuleB) ou refactorer
Pas de whitelist: truePropriétés injectées silencieusesToujours activer
new Service() manuelÉchappe au DI, non testableInjecter via constructeur
Secrets hardcodésFuite de sécuritéConfigService + .env validé par Joi
N+1 queriesPerf dégradéerelations dans findOne ou QueryBuilder
Pas de graceful shutdownRequêtes coupéesapp.enableShutdownHooks()

10. Bonnes pratiques 2026