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, OAu...
Scanned 5/27/2026
Install via CLI
openskills install vendeesign/codebloom---
name: backend
description: "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)
```
| Couche | Fait | Jamais |
|--------|------|--------|
| **Controller** | Parse request, valide input, appelle service, formate réponse | Business logic, requêtes DB |
| **Service** | Règles métier, orchestration, transactions | Types HTTP, accès DB direct |
| **Repository** | Requêtes DB, appels API externes | Business 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
| Code | Quand |
|------|-------|
| 200 | GET/PATCH/PUT success |
| 201 | POST avec Location header |
| 204 | DELETE sans contenu |
| 400 | Parse failure |
| 401 | Auth manquant |
| 403 | Permission refusée |
| 404 | Ressource introuvable |
| 409 | Conflit (doublon) |
| 422 | Validation échouée |
| 429 | Rate limit |
### Error envelope (RFC 9457)
```json
{
"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
| Besoin | Solution |
|--------|----------|
| Server → Client uniquement | **SSE** (le plus simple, passe les proxies) |
| Bidirectionnel | **WebSocket** (heartbeat + reconnection obligatoires) |
| Polling simple | **React Query `refetchInterval`** (pas d'infra) |
## File upload — Arbre de décision
| Taille | Solution |
|--------|----------|
| > 5MB | Presigned URL → upload direct S3 → sauver la référence (zéro charge serveur) |
| < 5MB | Multipart 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 faire | Faire |
|-------------|-------|
| Business logic dans les controllers | Service layer |
| `process.env` partout | Config centralisée typée |
| Erreurs génériques | Hiérarchie d'erreurs typées |
| DB direct dans controllers | Repository pattern |
| Pas de validation | Zod/Pydantic à la frontière |
| `console.log` | Logger JSON structuré |
| URL API hardcodée | Variable d'environnement |
| JWT dans localStorage | Memory + cookie httpOnly |
| Erreurs API brutes au client | Messages human-readable |
| Skip loading states | Skeleton/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é
No comments yet. Be the first to comment!