# Backend — Patterns : Contracts > Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet. --- ## Pattern : Contracts-First / Zod-Infer / No-DTO (monorepo TypeScript fullstack) - Objectif : avoir une seule source de vérité pour les contrats d'interface entre API et client, sans redéfinition manuelle de types. - Contexte : monorepo TypeScript avec un package partagé (`packages/contracts` ou équivalent), consommé par le backend et le front/mobile. - Quand l'utiliser : dès qu'une API est consommée par un client TypeScript dans le même repo. - Quand l'éviter : si le client est externe (autre organisation, autre langage) — dans ce cas, OpenAPI reste la référence. - Avantage : - Zéro drift entre contrat et implémentation - Types TypeScript gratuits via `z.infer<>` — aucune réécriture - Changement de contrat = erreur de compilation immédiate côté client - Mocks de tests alignés automatiquement - Limites / vigilance : - Ne pas mettre de logique métier dans `packages/contracts` (IO only) - Attention aux dépendances circulaires si le package grossit - Validé le : 07-03-2026 - Contexte technique : TypeScript / Zod / NestJS + Expo (React Native) — pattern agnostique framework ### Implémentation (exemple minimal) ```typescript // packages/contracts/src/auth/auth.schemas.ts export const RegisterRequestSchema = z.object({ email: z.string().email(), password: z.string().min(8), }); export type RegisterRequest = z.infer; // type GRATUIT // packages/contracts/src/index.ts export * from './auth/auth.schemas'; export * from './errors/error-code'; // apps/api/src/modules/auth/auth.controller.ts import type { RegisterRequest } from '@monrepo/contracts'; // + ZodValidationPipe → validation automatique, zéro DTO manuel // apps/mobile/src/domains/auth/auth.store.ts import type { RegisterRequest } from '@monrepo/contracts'; // même type, même schéma, zéro duplication ``` ### Structure cible du package contracts ``` packages/contracts/src/ auth/auth.schemas.ts ← request/response auth users/users.schemas.ts ← request/response users billing/billing.schemas.ts ← request/response billing (Epic suivant) errors/error-code.ts ← enum codes d'erreur stables http/envelopes.ts ← { data, meta } / { error, meta } index.ts ← re-export tout ``` ### Ce qui appartient à contracts - Schémas Zod request/response - Types inférés (`z.infer<>`) - Codes d'erreur applicatifs stables - Enums et constantes partagées (ex : liste officielle de sujets/topics) ### Ce qui n'appartient PAS à contracts - Logique métier - Modules/services/guards framework (NestJS, etc.) - State management client (Zustand, Redux, etc.) ### Checklist - [ ] Zéro DTO manuel dans l'API — uniquement `z.infer` - [ ] `ZodValidationPipe` global ou par endpoint pour la validation d'entrée - [ ] Constantes partagées (enums, listes) dans contracts, jamais dupliquées - [ ] Mocks de tests importent les types depuis contracts --- ## Pattern : Contracts-First — error codes comme contrat obligatoire - Objectif : maintenir les codes d'erreur API dans `packages/contracts` pour éviter les clients stringly-typed. - Contexte : monorepo TypeScript avec `packages/contracts/src/errors/error-code.ts`. - Règle : toute nouvelle erreur API ⇒ ajout obligatoire dans `error-code.ts` **avant merge**, pas après. - Risque si ignoré : clients qui testent des strings hardcodées au lieu d'importer l'enum → drift silencieux. - Validé le : 09-03-2026 - Contexte technique : TypeScript / NestJS + Expo (React Native) ### Checklist - [ ] Nouvel `error.code` → ajout dans `packages/contracts/src/errors/error-code.ts` en même commit - [ ] Clients importent l'enum, pas une string littérale - [ ] PR review : vérifier `error-code.ts` à chaque ajout d'endpoint d'erreur --- ## Pattern : Réponse HTTP 200 avec payload métier pour les états d'accès - Objectif : éviter les codes 4xx pour des états métier normaux qui nécessitent un rendu côté client. - Contexte : endpoints dont la réponse varie selon les droits ou l'état d'abonnement, sans que l'absence de contenu soit une erreur. - Quand l'utiliser : paywall, trial read-only, quota soft, état d'accès partiel — quand le client doit décider du rendu. - Quand l'éviter : accès réellement interdit côté serveur (403), non authentifié (401), endpoint inexistant (404). - Avantage : - pas de gestion d'exception côté client mobile pour des états courants - rendu conditionnel (paywall, teaser, empty) piloté par le payload - log serveur propre — 4xx réservés aux erreurs techniques/sécurité - Limites / vigilance : - ne pas généraliser aux vraies erreurs de sécurité — 401/403/404 gardent leur sémantique HTTP - Validé le : 20-03-2026 - Contexte technique : NestJS / Expo React Native — app-alexandrie story 4.1 ### Implémentation (exemple minimal) ```typescript // GET /community/forums // Sans abonnement → 200 + { data: { forums: [], paywallRequired: true }, meta } // Avec abonnement → 200 + { data: { forums: [...], paywallRequired: false }, meta } // ❌ Anti-pattern return res.status(402).json({ error: { code: 'SUBSCRIPTION_REQUIRED' } }); // ✅ Pattern correct return res.status(200).json({ data: { forums: [], paywallRequired: true }, meta: { total: 0 }, }); ``` ### Règle - **4xx** = erreur technique ou de sécurité (401 non authentifié, 403 accès interdit, 404 introuvable) - **200 + flag métier** = état métier normal que le client doit interpréter pour le rendu