Files
_Assistant_Lead_Tech/knowledge/backend/risques/nestjs.md
T
MaksTinyWorkshop f1b783407a docs(knowledge): capitalisation backend — intégration du triage local (mai-juin 2026)
Triage et intégration des propositions backend du buffer 95_a_capitaliser.md
(lot local RL799_V2 + app-alexandrie, mai-juin 2026), distinct de la capitalisation
remote antérieure (triage 2026-05-02).

~73 entrées intégrées sur knowledge/backend/, dont :
- patterns/auth.md : série "membrane d'auth fédérée BFF/OIDC" (9 patterns) + jose algo whitelist
- patterns/prisma.md : recette fusionnée "Migration String/Int → enum" (backfill + Cas A/B/C),
  row réactivable, endpoint replace atomique, updateMany conditionnel, etc.
- risques/general.md : 19 risques (epoch s vs ms, keepAliveTimeout=0, upsert+filtre liste,
  fail-safe catch-all, retrait asymétrique front/back, anti-énumération rate-limit, etc.)
- patterns/general, async, nestjs, contracts, tests + risques/auth, contracts, prisma, redis, stripe, tests
- compléments d'entrées existantes (authorize-after-fetch, P3014, cursor opaque, DI swc, Stripe v20...)
- README patterns/risques mis à jour

Doublons internes corrigés en relecture (suppression-champ .map() → general seul ;
e2e DB-based → tests.md seul). Doublons hors backend / entrées projet / rejets non intégrés.
Source 95_a_capitaliser.md non purgée à ce stade (purge en fin de capitalisation complète).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:25:02 +02:00

214 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Backend — Risques & vigilance : NestJS
domain: backend
bucket: risques
tags: [nestjs, controllers, guards, providers, review, injection, tsx-watch, mode-degrade]
applies_to: [implementation, review, debug]
severity: high
validated_on: 2026-06-25
source_projects: [app-alexandrie]
---
# Backend — Risques & vigilance : NestJS
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
---
<a id="risque-nestjs-toomanyrequest"></a>
## NestJS 11 — `TooManyRequestsException` inexistante
### Risques
- `TooManyRequestsException` n'est pas exportée par `@nestjs/common` en NestJS ≥ 11
- Erreur de compilation ou 500 si utilisée directement
### Symptômes
- `Cannot find name 'TooManyRequestsException'` à la compilation
- Test qui passe sur NestJS 10 mais échoue sur 11+
### Bonnes pratiques / mitigations
```typescript
// Pattern sûr pour HTTP 429
throw new HttpException(
{ error: { code: 'QUOTA_EXCEEDED', message: '...' } },
HttpStatus.TOO_MANY_REQUESTS,
);
```
- Contexte technique : NestJS v11+ — 20-03-2026
---
<a id="risque-controller-corrompu-insertions"></a>
## Controller NestJS corrompu par insertions multiples
### Risques
- Des méthodes imbriquées, décorateurs orphelins ou routes dupliquées cassent la syntaxe TypeScript sans que le compilateur ne l'attrape toujours
- La story est marquée "completed" alors que le code ne compile pas
### Symptômes
- `@Get('/route')` apparaît dans le corps d'une autre méthode
- La même route est déclarée 2-3 fois dans le même controller
- Erreur NestJS au runtime mais pas à la compilation
### Bonnes pratiques / mitigations
- Quand on ajoute >3 endpoints à un controller existant, réécrire le fichier entier en partant du fichier original
- Ne jamais insérer par blocs séparés — la concaténation casse la structure AST
- **Checklist review** : grep `@Get\|@Post\|@Patch\|@Delete` dans le controller et vérifier qu'aucune route n'est dupliquée
- Contexte technique : NestJS / TypeScript — app-alexandrie 20-03-2026
---
<a id="risque-repository-dead-layer"></a>
## Repository layer non branché (dead layer)
### Risques
- Donner une impression de sécurité alors que le code métier continue d'appeler l'ORM directement
- Multiplier les chemins d'accès aux données avec des règles différentes
- Payer le coût d'une abstraction qui n'a aucun effet réel
### Symptômes
- Un repository est créé mais les anciens call sites Prisma restent en place
- Les nouvelles règles de scoping ou de sécurité ne s'appliquent pas partout
- La review montre des fichiers de repository peu ou jamais importés
### Bonnes pratiques / mitigations
- Vérifier qu'une nouvelle couche d'abstraction est réellement branchée dans les call sites existants
- Rechercher explicitement les appels directs restants lors de la review
- Refuser l'introduction d'une couche repository tant que la migration effective n'est pas faite
- Contexte technique : TypeScript / Prisma / refactor d'accès aux données — 16-03-2026
---
<a id="risque-interface-provider-incomplete"></a>
## Interface provider incomplète ou divergente de ses implémentations
### Risques
- Une implémentation expose des méthodes non déclarées dans le contrat commun
- Les appelants contournent l'interface et se couplent à un provider concret
- Une stratégie provider devient non interchangeable en pratique
### Symptômes
- Appels avec cast ou accès direct à une implémentation spécifique
- Méthodes présentes dans une classe mais absentes de l'interface
- Régression lors d'un changement de provider
### Bonnes pratiques / mitigations
- Toute capacité commune attendue par les appelants doit être déclarée dans l'interface
- Interdire les méthodes "cachées" consommées hors contrat
- Tester au moins une implémentation par le contrat abstrait
- Contexte technique : TypeScript / provider strategy — 10-03-2026
---
<a id="risque-guard-multistatut-read-methods"></a>
## Guard multi-statut : ordre READ_METHODS avant statut et whitelist des writes critiques
### Risques
- Un guard qui vérifie le statut BLOCKED avant la méthode HTTP bloque aussi les GET, enfermant l'utilisateur sans moyen de sortir (ex : révoquer un appareil).
- Sans whitelist explicite des writes autorisés en état bloqué, le circuit de sortie est fermé.
### Symptômes
- L'utilisateur avec `sessionStatus === 'BLOCKED'` ne peut pas accéder à `GET /auth/devices`
- Tests passent mais l'ordre des conditions est inversé
### Bonnes pratiques / mitigations
```typescript
// ❌ DANGEREUX — BLOCKED bloque aussi les GET
if (user.sessionStatus === 'BLOCKED') throw new HttpException(...);
if (READ_METHODS.has(request.method)) return true; // jamais atteint pour BLOCKED
// ✅ CORRECT
if (READ_METHODS.has(request.method)) return true;
if (isDeviceManagementPath(request.path)) return true; // whitelist writes critiques
if (user.sessionStatus === 'BLOCKED') throw new HttpException(...);
```
- **Règle** : dans tout guard multi-statut, vérifier `READ_METHODS.has(request.method)` EN PREMIER, avant tout check de statut.
- **Règle** : définir une whitelist explicite des writes autorisés même en état bloqué.
- Contexte technique : NestJS / guard statut — app-alexandrie 30-03-2026
---
<a id="risque-bootstrap-ok-injection-cassee-tsx-watch"></a>
## Bootstrap NestJS OK mais injection runtime cassée sous `tsx watch`
### Risques
- En dev, un serveur NestJS peut afficher `Nest application successfully started` et exposer ses routes, tout en cassant à la **première requête** : des dépendances injectées (`Reflector`, services consommés par les guards globaux) arrivent à `undefined` au runtime.
- Le symptôme initial ressemble à un problème réseau/mobile alors que la cause réelle est l'injection backend.
### Symptômes
- `GET /` ou une route d'auth répond `500` alors que le bootstrap est nominal
- Stack runtime du type `Cannot read properties of undefined (reading 'getAllAndOverride')`
- Les guards globaux (`AuthGuard`, guards basés sur `Reflector`) sont les premiers à tomber
### Bonnes pratiques / mitigations
- Ne pas conclure trop vite à un problème réseau dès que le port écoute : traiter « le port écoute » et « les requêtes fonctionnent » comme **deux validations distinctes**.
- Tester immédiatement `GET /`, l'endpoint OpenAPI et une route publique métier, puis lire la stack runtime **après le premier hit** (pas seulement les logs de bootstrap).
- Si un lanceur `tsx watch` est utilisé avec NestJS, vérifier explicitement la compatibilité avec l'injection runtime ; en cas de doute, expliciter les injections critiques avec `@Inject(...)` sur guards et services exposés dès le premier hit.
#### Cause racine : esbuild n'émet pas `emitDecoratorMetadata`
Tout runner basé sur esbuild (`tsx watch`, `tsup --watch`, `esbuild-node-runner`...) **n'implémente pas** `emitDecoratorMetadata`. Sans cette metadata, Nest ne sait plus quels types injecter dans les constructeurs `@Injectable()` et tombe dans un fallback **silencieux** : `new Service(undefined, undefined, …)`. Aucun crash au boot ; tous les services à >1 dépendance deviennent inopérants au runtime, le bug n'apparaît qu'au **premier appel** d'une méthode touchant une dépendance injectée.
Le piège est durable : la CI passe (elle utilise `nest build` = tsc), les e2e passent (AppModule allégé). Le bug ne se voit qu'au navigateur/mobile, sur les endpoints non couverts par e2e. Cas vécu : introduit par bascule `nest start --watch``tsx watch src/main.ts` (gain de boot / contournement conflit Prisma v7 ESM/CJS), non détecté 2 mois et demi.
**Détection en 30 s** : `console.log({ deps: typeof someDep })` dans le constructor du service qui crashe. Si `typeof === 'undefined'` au boot alors que le module l'importe → c'est le bug metadata.
#### Fix recommandé (Nest 11) : `nest start --watch --builder swc`
- `pnpm add -D @swc/cli @swc/core`
- `.swcrc` avec `jsc.transform.legacyDecorator: true`, `jsc.transform.decoratorMetadata: true`, `module.type: "commonjs"` (préserve decorators + metadata)
- `nest-cli.json` : `"compilerOptions": { "builder": "swc", "typeCheck": true }` (le `typeCheck: true` relance `tsc --noEmit` en parallèle pour garder la sécurité types)
- `package.json` : conserver `"start:dev": "nest start --watch"` (le `nest-cli.json` prend le relais pour le builder)
- Si pnpm bloque le binaire natif SWC : `onlyBuiltDependencies: ['@swc/core']` dans `pnpm-workspace.yaml`
Bénéfices : DI nominale, type-check préservé, build ~10× plus rapide que tsc seul (≈190 ms pour 153 fichiers), résout au passage le conflit Prisma v7 ESM/CJS.
**Anti-pattern à proscrire** : plugin esbuild qui prétend réimplémenter `emitDecoratorMetadata` (ex : `@anatine/esbuild-decorators`) — couverture partielle, casse silencieusement sur les types génériques. Sa présence est un signal qu'il faut passer à SWC.
- Contexte technique : NestJS / tsx watch / esbuild / SWC / injection — app-alexandrie 01-04-2026, complété 26-05-2026
---
<a id="risque-writemodeguard-bloque-endpoints-support"></a>
## Guard d'écriture en mode dégradé bloque les endpoints de support
### Risques
- Un guard global qui refuse toutes les écritures (`POST/PUT/PATCH/DELETE`) quand un service critique est down (ex : Redis) bloque aussi les endpoints dont l'utilité est **maximale en panne** : soumission de ticket de support, feedback, remontée de logs d'erreur client.
- Cas typique : un endpoint de ticketing (`POST /support/...`) devient inaccessible précisément au moment où l'utilisateur veut signaler le dysfonctionnement qu'il subit.
### Symptômes
- Soumission de ticket de support impossible quand le service de quota/cache est indisponible
- Le guard rejette des routes qui devraient rester ouvertes en mode dégradé
### Bonnes pratiques / mitigations
- Lors de l'ajout d'un guard d'écriture en mode dégradé, **recenser explicitement les endpoints critiques qui doivent rester disponibles** : support, feedback, logs d'erreur client.
- Whitelister ces préfixes dès la création du guard — pas lors de la code review d'une story ultérieure. Exemple de whitelist : `/auth`, `/billing/.../webhooks`, `/support`.
- **Why** : un service de ticketing bloqué quand le backend dysfonctionne est contre-productif — l'utilisateur ne peut pas signaler le problème qu'il est en train de subir.
- Contexte technique : NestJS / guard mode dégradé — app-alexandrie 01-04-2026