backend

Patterns backend/fullstack. Se charge à l'écriture/modification de code serveur : routes, controllers, services, models, middleware, migrations, jobs, seeds, schemas. Frameworks : Express, Fastify, NestJS, Koa, Hono (Node) ; FastAPI, Django, Flask (Python) ; Laravel, Symfony, Slim (PHP) ; Rails (Ruby) ; Spring, Quarkus (Java) ; Gin, Echo, Fiber (Go) ; Actix, Axum (Rust) ; ASP.NET Core (C#). Couvre : API REST, architecture 3 couches (controller → service → repository), auth (sessions, JWT, OAuth), base de données (SQL, ORM, migrations), error handling structuré, middleware, real-time (WebSocket, SSE), file upload, validation entrées. Ne se charge PAS quand : discussion, review ou documentation sans écriture de code backend.

Fullstack Patterns — Architecture backend solide

Patterns éprouvés pour tout projet backend ou fullstack. Se charge automatiquement quand du code touche des routes, controllers, services, base de données, auth ou API.

Architecture 3 couches

Controller (HTTP) → Service (Business) → Repository (Data Access)
CoucheFaitJamais
ControllerParse request, valide input, appelle service, formate réponseBusiness logic, requêtes DB
ServiceRègles métier, orchestration, transactionsTypes HTTP, accès DB direct
RepositoryRequêtes DB, appels API externesBusiness logic, types HTTP

Organiser par feature, pas par couche technique. orders/ contient controller + service + repository + tests, pas controllers/, services/, repositories/.

7 règles de fer

  1. Controllers minces — délèguent aux services, jamais de logique métier
  2. Services isolés du HTTP — jamais d'import de types Request/Response
  3. Config par variables d'env — validées au démarrage, fail fast
  4. Erreurs typées — chaque erreur a un type, un log, un format de réponse cohérent
  5. Validation aux frontières — tout input validé à l'entrée (Zod, Joi, Pydantic)
  6. Logging structuré JSON — avec request ID propagé par middleware
  7. Injection de dépendances — constructeur, jamais d'instanciation dans les services

Ordre des middlewares

RequestID → Logging → CORS → RateLimit → BodyParse → Auth → Authz
→ Validation → Handler → ErrorHandler → Response

API REST — Conventions

Nommage

  • URLs : noms pluriels, kebab-case → /order-items, pas /getOrderItems
  • JSON : camelCase → firstName
  • Headers : Train-Case → X-Request-Id
  • Max 2 niveaux de nesting → /orders/123/items. Au-delà : query params → GET /reviews?orderId=123

Status codes

CodeQuand
200GET/PATCH/PUT success
201POST avec Location header
204DELETE sans contenu
400Parse failure
401Auth manquant
403Permission refusée
404Ressource introuvable
409Conflit (doublon)
422Validation échouée
429Rate limit

Error envelope (RFC 9457)

{
  "type": "https://api.example.com/errors/insufficient-funds",
  "title": "Insufficient Funds",
  "status": 422,
  "detail": "Account balance $10.00 < withdrawal $50.00",
  "request_id": "req_7f3a8b2c"
}

Toutes les erreurs suivent ce format. Request ID toujours inclus.

Pagination

Cursor (préféré pour gros datasets) : retourne next_cursor + has_more Offset : pour datasets stables et petits

  • Default : 20 items, max : 100
  • Filtres : ?status=shipped, ?price_gte=10&price_lte=100
  • Tri : ?sort=-created_at (- = descendant)
  • Champs : ?fields=id,name

Authentification

Token pattern

  • Access token court (15min) + refresh token server-side
  • Stockage : mémoire + cookie httpOnly pour refresh — jamais localStorage (XSS)
  • Claims minimaux : userId + roles, pas l'objet user entier
  • Credentials dans header Authorization: Bearer {token} — jamais en query param (visible dans les logs)

Refresh flow

Requête → 401 → intercepteur attrape → POST /api/auth/refresh (credentials: include)
→ nouveau token → retry requête originale (transparent pour l'UI)

CORS

  • Origins explicites en production — jamais * avec credentials
  • Frontend : credentials: 'include' pour envoyer les cookies cross-domain

Base de données

Schema design

  • IDs : UUID pour les IDs publics (API), serial pour les clés internes (joins) — hybride sécurité + performance
  • Normalisation : commencer en 3NF, dénormaliser seulement avec preuves mesurées de problème de perf
  • Migrations : toujours réversibles, jamais de SQL manuel

Indexing

  • Composite index = leftmost prefix : (A, B, C) sert A et A+B, pas B seul
  • Toujours indexer les foreign keys
  • Index partiels pour les sous-ensembles (ex: seulement les enregistrements actifs)
  • Multi-tenant : tenant_id dans chaque table, leftmost dans composite index

Connection pooling

Pool size de départ : (CPU cores x 2) + nombre de disques

Real-time — Arbre de décision

BesoinSolution
Server → Client uniquementSSE (le plus simple, passe les proxies)
BidirectionnelWebSocket (heartbeat + reconnection obligatoires)
Polling simpleReact Query refetchInterval (pas d'infra)

File upload — Arbre de décision

TailleSolution
> 5MBPresigned URL → upload direct S3 → sauver la référence (zéro charge serveur)
< 5MBMultipart form → stream through server

Error handling

Pattern

// Créer des erreurs typées par domaine
class NotFoundError extends AppError { status = 404 }
class ValidationError extends AppError { status = 422 }

// Global middleware attrape tout
app.use((err, req, res, next) => {
  if (err instanceof AppError) {
    // Opérationnel → réponse structurée
    return res.status(err.status).json(err.toRFC9457())
  }
  // Inattendu → log + 500 générique
  logger.error({ err, requestId: req.id })
  res.status(500).json({ title: 'Internal Server Error', request_id: req.id })
})
  • Erreurs opérationnelles (attendues) → réponse structurée avec status code
  • Erreurs de programmation (inattendues) → log + 500 générique au client
  • Jamais de stack traces au frontend
  • Jamais de retry sur 4xx (l'erreur client ne changera pas) — retry uniquement sur 5xx

Anti-patterns

Ne pas faireFaire
Business logic dans les controllersService layer
process.env partoutConfig centralisée typée
Erreurs génériquesHiérarchie d'erreurs typées
DB direct dans controllersRepository pattern
Pas de validationZod/Pydantic à la frontière
console.logLogger JSON structuré
URL API hardcodéeVariable d'environnement
JWT dans localStorageMemory + cookie httpOnly
Erreurs API brutes au clientMessages human-readable
Skip loading statesSkeleton/spinner

Production hardening checklist

  • Health check endpoints (/health liveness, /ready readiness)
  • Graceful shutdown (SIGTERM → drain connections, close DB)
  • CORS : origins explicites, jamais *
  • Security headers (helmet ou équivalent)
  • Rate limiting sur les endpoints publics
  • Validation sur TOUS les endpoints
  • HTTPS forcé
  • Erreurs internes jamais exposées au client

Tool Boundaries

  • Appliquer ces patterns dans le stack existant du projet
  • Ne pas migrer de framework ou d'ORM sauf demande explicite
  • Ne pas restructurer un projet entier — améliorer ce qui est touché