refactor: move ErrorHandlerService/HttpError out of express-tools
ErrorHandlerService has zero dependency on Express — it's a plain
"map an error to {status, body}" service that works identically
behind any HTTP framework. It had no business living in a package
named express-tools.
Extracted HttpError, ErrorHandlerService, and ErrorHandlingResult
into a new packages/error-tools package (same tsc-build-to-dist
pattern as shared/express-tools). express-tools now only keeps the
actual Express-specific layer: ExpressServer, wrapAsyncHandler, and
createErrorMiddleware (which adapts ErrorHandlerService, imported
from error-tools, onto Express).
- packages/error-tools: new package, depends on shared + zod
- packages/express-tools: drops zod dependency, adds error-tools
dependency for error-middleware.ts's type import
- apps/api: adds error-tools dependency; app.ts, auth.service.ts,
require-auth.ts now import HttpError/errorHandlerService from
error-tools instead of express-tools
- apps/api/Dockerfile: adds COPY for packages/error-tools in the
runtime stage
- specs/error-handling.md, specs/backend-architecture.md, README.md
updated to reflect the new package split
Verified: pnpm lint, pnpm build (all packages, correct dependency
order), pnpm test (9/9 Mocha), pnpm test:bdd (5/5 Cucumber), full
Docker rebuild + compose up (no crash-loop), curl + browser checks
of /health, unknown-route 404, signup (201), duplicate-email 409
(code 4001 EMAIL_ALREADY_IN_USE) — all going through the moved
ErrorHandlerService/HttpError correctly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
3a5dc83bf9
commit
95bd8fab87
17 changed files with 153 additions and 61 deletions
28
README.md
28
README.md
|
|
@ -11,15 +11,20 @@ Monorepo pnpm workspaces :
|
||||||
`loginSchema`), types (`SafeUserProfile`), et le contrat d'erreurs (`ErrorCode`
|
`loginSchema`), types (`SafeUserProfile`), et le contrat d'erreurs (`ErrorCode`
|
||||||
numérique, `ApiErrorResponse`, voir [specs/error-handling.md](specs/error-handling.md)) —
|
numérique, `ApiErrorResponse`, voir [specs/error-handling.md](specs/error-handling.md)) —
|
||||||
même règles des deux côtés, pas de risque de dérive entre front et back.
|
même règles des deux côtés, pas de risque de dérive entre front et back.
|
||||||
|
- `packages/error-tools` — gestion des erreurs, **indépendante de tout framework
|
||||||
|
HTTP** (n'importe pas `express`) : `HttpError`, `ErrorHandlerService`. Séparé
|
||||||
|
d'`express-tools` précisément parce que rien ici ne dépend d'Express. Détail :
|
||||||
|
[specs/error-handling.md](specs/error-handling.md).
|
||||||
- `packages/express-tools` — outillage Express générique et réutilisable : `ExpressServer`
|
- `packages/express-tools` — outillage Express générique et réutilisable : `ExpressServer`
|
||||||
(init serveur, routes, middlewares), `wrapAsyncHandler`, `HttpError`,
|
(init serveur, routes, middlewares), `wrapAsyncHandler`, `createErrorMiddleware`
|
||||||
`ErrorHandlerService`, `createErrorMiddleware` — séparé d'`apps/api`, pas de
|
(adapte `ErrorHandlerService` de `error-tools` à Express) — séparé d'`apps/api`,
|
||||||
logique métier. Détail : [specs/backend-architecture.md](specs/backend-architecture.md).
|
pas de logique métier. Détail : [specs/backend-architecture.md](specs/backend-architecture.md).
|
||||||
|
|
||||||
`packages/shared` et `packages/express-tools` ont un vrai build (`tsc` → `dist/`,
|
`packages/shared`, `packages/error-tools` et `packages/express-tools` ont un vrai
|
||||||
voir leur `package.json`) : consommés en JS compilé, pas en TS brut — nécessaire
|
build (`tsc` → `dist/`, voir leur `package.json`) : consommés en JS compilé, pas en
|
||||||
pour un runtime Node pur (Docker, pas de transpilation à la volée), voir la note
|
TS brut — nécessaire pour un runtime Node pur (Docker, pas de transpilation à la
|
||||||
dans [specs/frontend-architecture.md](specs/frontend-architecture.md#note-sur-les-fichiers-dts).
|
volée), voir la note dans
|
||||||
|
[specs/frontend-architecture.md](specs/frontend-architecture.md#note-sur-les-fichiers-dts).
|
||||||
|
|
||||||
## Prérequis
|
## Prérequis
|
||||||
|
|
||||||
|
|
@ -195,10 +200,11 @@ found, `500x` interne — et `ApiErrorResponse`) : l'API renvoie toujours
|
||||||
`{ code, message, details? }` (message en anglais, dev-facing — jamais affiché tel
|
`{ code, message, details? }` (message en anglais, dev-facing — jamais affiché tel
|
||||||
quel), et le client traduit `code` en libellé français via **i18next**
|
quel), et le client traduit `code` en libellé français via **i18next**
|
||||||
(`ErrorMessageService`, `apps/web/src/services/error-message.service.ts` →
|
(`ErrorMessageService`, `apps/web/src/services/error-message.service.ts` →
|
||||||
`apps/web/src/locales/fr/translation.json`). Côté API, `ErrorHandlerService` et
|
`apps/web/src/locales/fr/translation.json`). Côté API, `ErrorHandlerService`
|
||||||
`createErrorMiddleware` (`packages/express-tools`) centralisent la transformation
|
(`packages/error-tools`) et `createErrorMiddleware` (`packages/express-tools`)
|
||||||
de toute erreur levée en réponse HTTP conforme — aucune valeur `ErrorCode` codée
|
centralisent la transformation de toute erreur levée en réponse HTTP conforme —
|
||||||
en dur nulle part (toujours `ErrorCode.XXX`, y compris dans les mocks Cypress).
|
aucune valeur `ErrorCode` codée en dur nulle part (toujours `ErrorCode.XXX`, y
|
||||||
|
compris dans les mocks Cypress).
|
||||||
|
|
||||||
Détail complet (schéma, exemples, comment ajouter un nouveau code d'erreur) :
|
Détail complet (schéma, exemples, comment ajouter un nouveau code d'erreur) :
|
||||||
[specs/error-handling.md](specs/error-handling.md).
|
[specs/error-handling.md](specs/error-handling.md).
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ COPY --from=build /repo/node_modules ./node_modules
|
||||||
COPY --from=build /repo/package.json ./package.json
|
COPY --from=build /repo/package.json ./package.json
|
||||||
COPY --from=build /repo/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
COPY --from=build /repo/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||||
COPY --from=build /repo/packages/shared ./packages/shared
|
COPY --from=build /repo/packages/shared ./packages/shared
|
||||||
|
COPY --from=build /repo/packages/error-tools ./packages/error-tools
|
||||||
COPY --from=build /repo/packages/express-tools ./packages/express-tools
|
COPY --from=build /repo/packages/express-tools ./packages/express-tools
|
||||||
COPY --from=build /repo/apps/api/node_modules ./apps/api/node_modules
|
COPY --from=build /repo/apps/api/node_modules ./apps/api/node_modules
|
||||||
COPY --from=build /repo/apps/api/dist ./apps/api/dist
|
COPY --from=build /repo/apps/api/dist ./apps/api/dist
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"postinstall": "prisma generate"
|
"postinstall": "prisma generate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@batch-cooking/error-tools": "workspace:*",
|
||||||
"@batch-cooking/express-tools": "workspace:*",
|
"@batch-cooking/express-tools": "workspace:*",
|
||||||
"@batch-cooking/shared": "workspace:*",
|
"@batch-cooking/shared": "workspace:*",
|
||||||
"@prisma/client": "^5.22.0",
|
"@prisma/client": "^5.22.0",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import {
|
import { errorHandlerService } from "@batch-cooking/error-tools";
|
||||||
ExpressServer,
|
import { ExpressServer, createErrorMiddleware } from "@batch-cooking/express-tools";
|
||||||
createErrorMiddleware,
|
|
||||||
errorHandlerService,
|
|
||||||
} from "@batch-cooking/express-tools";
|
|
||||||
import { ErrorCode } from "@batch-cooking/shared";
|
import { ErrorCode } from "@batch-cooking/shared";
|
||||||
import type { Express, Request, Response } from "express";
|
import type { Express, Request, Response } from "express";
|
||||||
import { env } from "./config/env.js";
|
import { env } from "./config/env.js";
|
||||||
|
|
@ -34,7 +31,7 @@ export function createServer(): ExpressServer {
|
||||||
|
|
||||||
// Final error-handling middleware: every thrown/`next(err)`-ed error in
|
// Final error-handling middleware: every thrown/`next(err)`-ed error in
|
||||||
// the app ends up here. All the "what status/body does this error map
|
// the app ends up here. All the "what status/body does this error map
|
||||||
// to" logic lives in ErrorHandlerService, from @batch-cooking/express-tools
|
// to" logic lives in ErrorHandlerService, from @batch-cooking/error-tools
|
||||||
// — this stays a thin adapter.
|
// — this stays a thin adapter.
|
||||||
server.setErrorHandler(createErrorMiddleware(errorHandlerService));
|
server.setErrorHandler(createErrorMiddleware(errorHandlerService));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { HttpError } from "@batch-cooking/express-tools";
|
import { HttpError } from "@batch-cooking/error-tools";
|
||||||
import { ErrorCode, type SafeUserProfile } from "@batch-cooking/shared";
|
import { ErrorCode, type SafeUserProfile } from "@batch-cooking/shared";
|
||||||
import type { NextFunction, Request, Response } from "express";
|
import type { NextFunction, Request, Response } from "express";
|
||||||
import { env } from "../config/env.js";
|
import { env } from "../config/env.js";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { HttpError } from "@batch-cooking/express-tools";
|
import { HttpError } from "@batch-cooking/error-tools";
|
||||||
import { ErrorCode, type LoginInput, type SignupInput } from "@batch-cooking/shared";
|
import { ErrorCode, type LoginInput, type SignupInput } from "@batch-cooking/shared";
|
||||||
import type { UserProfile } from "@prisma/client";
|
import type { UserProfile } from "@prisma/client";
|
||||||
import argon2 from "argon2";
|
import argon2 from "argon2";
|
||||||
|
|
|
||||||
27
packages/error-tools/package.json
Normal file
27
packages/error-tools/package.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "@batch-cooking/error-tools",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"no tests yet\" && exit 0",
|
||||||
|
"build": "tsc -p tsconfig.json",
|
||||||
|
"postinstall": "tsc -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.9.0",
|
||||||
|
"typescript": "^5.7.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@batch-cooking/shared": "workspace:*",
|
||||||
|
"zod": "^3.25.76"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ import { type ApiErrorResponse, ErrorCode } from "@batch-cooking/shared";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
import { HttpError } from "./http-error.js";
|
import { HttpError } from "./http-error.js";
|
||||||
|
|
||||||
/** Return value of {@link ErrorHandlerService.handle}: everything an Express error middleware needs to send a response. */
|
/** Return value of {@link ErrorHandlerService.handle}: everything a caller needs to send an HTTP response. */
|
||||||
export interface ErrorHandlingResult {
|
export interface ErrorHandlingResult {
|
||||||
/** HTTP status code to respond with. */
|
/** HTTP status code to respond with. */
|
||||||
status: number;
|
status: number;
|
||||||
|
|
@ -12,9 +12,11 @@ export interface ErrorHandlingResult {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Centralizes every "how do we turn a thrown error into an HTTP response"
|
* Centralizes every "how do we turn a thrown error into an HTTP response"
|
||||||
* decision for an Express app in one place, so route handlers and the
|
* decision in one place, so route handlers and framework-specific
|
||||||
* error middleware never duplicate this logic. Wire it into Express via
|
* middleware (e.g. `createErrorMiddleware` in `@batch-cooking/express-tools`)
|
||||||
* {@link createErrorMiddleware} (see error-middleware.ts).
|
* never duplicate this logic. Framework-agnostic on purpose — it only maps
|
||||||
|
* an error to `{ status, body }` and has zero dependency on Express or any
|
||||||
|
* other HTTP framework.
|
||||||
*
|
*
|
||||||
* Recognizes three error shapes today (zod validation failures, our own
|
* Recognizes three error shapes today (zod validation failures, our own
|
||||||
* `HttpError`, and anything else) and always falls back to a safe, generic
|
* `HttpError`, and anything else) and always falls back to a safe, generic
|
||||||
|
|
@ -7,7 +7,7 @@ import type { ErrorCode } from "@batch-cooking/shared";
|
||||||
* Route handlers throw this (or let it bubble from a service call) instead
|
* Route handlers throw this (or let it bubble from a service call) instead
|
||||||
* of manually setting a status/body — {@link ErrorHandlerService} is the
|
* of manually setting a status/body — {@link ErrorHandlerService} is the
|
||||||
* single place that turns it into an actual HTTP response, so every error
|
* single place that turns it into an actual HTTP response, so every error
|
||||||
* path in an Express app built with these tools is shaped consistently.
|
* path in an app built with these tools is shaped consistently.
|
||||||
*/
|
*/
|
||||||
export class HttpError extends Error {
|
export class HttpError extends Error {
|
||||||
/** HTTP status code to respond with (e.g. 401, 404, 409). */
|
/** HTTP status code to respond with (e.g. 401, 404, 409). */
|
||||||
11
packages/error-tools/src/index.ts
Normal file
11
packages/error-tools/src/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Public entry point of the framework-agnostic error-handling tooling
|
||||||
|
// shared across apps in this monorepo. Everything here — HttpError,
|
||||||
|
// ErrorHandlerService — has zero dependency on Express or any other HTTP
|
||||||
|
// framework; it only knows how to map an error to a {status, body} pair.
|
||||||
|
//
|
||||||
|
// Framework-specific adapters (e.g. Express's `createErrorMiddleware`) live
|
||||||
|
// in their own package (`@batch-cooking/express-tools`) and consume these
|
||||||
|
// types instead of duplicating the mapping logic.
|
||||||
|
|
||||||
|
export * from "./error-handler.service.js";
|
||||||
|
export * from "./http-error.js";
|
||||||
12
packages/error-tools/tsconfig.json
Normal file
12
packages/error-tools/tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
|
@ -24,10 +24,10 @@
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@batch-cooking/error-tools": "workspace:*",
|
||||||
"@batch-cooking/shared": "workspace:*",
|
"@batch-cooking/shared": "workspace:*",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1"
|
||||||
"zod": "^3.25.76"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import type { ErrorHandlerService } from "@batch-cooking/error-tools";
|
||||||
import type { NextFunction, Request, Response } from "express";
|
import type { NextFunction, Request, Response } from "express";
|
||||||
import type { ErrorHandlerService } from "./error-handler.service.js";
|
|
||||||
|
|
||||||
/** Express error-handling middleware signature (the 4-arg form Express detects as an error handler). */
|
/** Express error-handling middleware signature (the 4-arg form Express detects as an error handler). */
|
||||||
type ExpressErrorMiddleware = (
|
type ExpressErrorMiddleware = (
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,11 @@
|
||||||
// infrastructure lives here — domain-specific code (auth, business logic)
|
// infrastructure lives here — domain-specific code (auth, business logic)
|
||||||
// stays in the consuming app.
|
// stays in the consuming app.
|
||||||
//
|
//
|
||||||
// Note the split: ErrorHandlerService (error-handler.service.ts) has zero
|
// Framework-agnostic error-handling pieces (ErrorHandlerService, HttpError)
|
||||||
// dependency on Express — it's a plain "map an error to {status, body}"
|
// live in `@batch-cooking/error-tools` instead, since they have zero
|
||||||
// service that would work identically behind Fastify or any other
|
// dependency on Express. `createErrorMiddleware` here is the thin Express
|
||||||
// framework. ExpressServer and createErrorMiddleware are the actual
|
// adapter that wires that service into an Express app.
|
||||||
// Express-specific layer, adapting framework-agnostic pieces (like
|
|
||||||
// ErrorHandlerService) onto Express's API.
|
|
||||||
|
|
||||||
export * from "./async-handler.js";
|
export * from "./async-handler.js";
|
||||||
export * from "./error-handler.service.js";
|
|
||||||
export * from "./error-middleware.js";
|
export * from "./error-middleware.js";
|
||||||
export * from "./express-server.js";
|
export * from "./express-server.js";
|
||||||
export * from "./http-error.js";
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ importers:
|
||||||
|
|
||||||
apps/api:
|
apps/api:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@batch-cooking/error-tools':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/error-tools
|
||||||
'@batch-cooking/express-tools':
|
'@batch-cooking/express-tools':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/express-tools
|
version: link:../../packages/express-tools
|
||||||
|
|
@ -134,8 +137,27 @@ importers:
|
||||||
specifier: ^5.4.11
|
specifier: ^5.4.11
|
||||||
version: 5.4.21(@types/node@22.20.1)(sass@1.102.0)
|
version: 5.4.21(@types/node@22.20.1)(sass@1.102.0)
|
||||||
|
|
||||||
|
packages/error-tools:
|
||||||
|
dependencies:
|
||||||
|
'@batch-cooking/shared':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../shared
|
||||||
|
zod:
|
||||||
|
specifier: ^3.25.76
|
||||||
|
version: 3.25.76
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^22.9.0
|
||||||
|
version: 22.20.1
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.7.2
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages/express-tools:
|
packages/express-tools:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@batch-cooking/error-tools':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../error-tools
|
||||||
'@batch-cooking/shared':
|
'@batch-cooking/shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../shared
|
version: link:../shared
|
||||||
|
|
@ -148,9 +170,6 @@ importers:
|
||||||
express:
|
express:
|
||||||
specifier: ^4.21.1
|
specifier: ^4.21.1
|
||||||
version: 4.22.2
|
version: 4.22.2
|
||||||
zod:
|
|
||||||
specifier: ^3.25.76
|
|
||||||
version: 3.25.76
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/cookie-parser':
|
'@types/cookie-parser':
|
||||||
specifier: ^1.4.10
|
specifier: ^1.4.10
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Architecture backend — Projet Batch-cooking
|
# Architecture backend — Projet Batch-cooking
|
||||||
|
|
||||||
> Documentation de l'organisation d'`apps/api` et de l'outillage partagé
|
> Documentation de l'organisation d'`apps/api` et de l'outillage partagé
|
||||||
> (`packages/express-tools`, `packages/shared`).
|
> (`packages/express-tools`, `packages/error-tools`, `packages/shared`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -54,15 +54,17 @@ Sans ça, une exception dans un handler `async` ne remonte jamais tout seule au
|
||||||
middleware d'erreur d'Express — chaque route devait faire son propre
|
middleware d'erreur d'Express — chaque route devait faire son propre
|
||||||
`try { ... } catch (err) { next(err); }`. `wrapAsyncHandler` l'automatise.
|
`try { ... } catch (err) { next(err); }`. `wrapAsyncHandler` l'automatise.
|
||||||
|
|
||||||
### `HttpError` / `ErrorHandlerService` / `createErrorMiddleware`
|
### `createErrorMiddleware` — adaptateur Express pour `packages/error-tools`
|
||||||
|
|
||||||
Voir [error-handling.md](./error-handling.md) pour le détail. À noter :
|
Voir [error-handling.md](./error-handling.md) pour le détail. `HttpError` et
|
||||||
|
`ErrorHandlerService` vivent dans **`packages/error-tools`**, pas ici :
|
||||||
`ErrorHandlerService` **n'a aucune dépendance à Express** — c'est un service
|
`ErrorHandlerService` **n'a aucune dépendance à Express** — c'est un service
|
||||||
générique `erreur → { status, body }` qui fonctionnerait à l'identique derrière
|
générique `erreur → { status, body }` qui fonctionnerait à l'identique derrière
|
||||||
Fastify ou n'importe quel autre framework. `ExpressServer` et
|
Fastify ou n'importe quel autre framework, donc il n'a rien à faire dans un
|
||||||
`createErrorMiddleware` sont la vraie couche Express : elles adaptent des
|
package *express*-tools. `ExpressServer` et `createErrorMiddleware` (ici) sont
|
||||||
pièces indépendantes du framework (comme `ErrorHandlerService`) à l'API
|
la vraie couche Express : elles adaptent des pièces indépendantes du framework
|
||||||
d'Express. C'est pour ça que `ErrorHandlerService` n'importe jamais `express`.
|
(`ErrorHandlerService`, importé depuis `@batch-cooking/error-tools`) à l'API
|
||||||
|
d'Express.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,20 +14,29 @@ l'affichage utilisateur, passe par un chemin unique et prévisible :
|
||||||
réponse d'erreur de l'API). Ni l'API ni le web ne définissent leur propre liste
|
réponse d'erreur de l'API). Ni l'API ni le web ne définissent leur propre liste
|
||||||
de codes, et aucune valeur n'est jamais codée en dur ailleurs (toujours
|
de codes, et aucune valeur n'est jamais codée en dur ailleurs (toujours
|
||||||
`ErrorCode.XXX`, jamais un nombre/une chaîne littérale).
|
`ErrorCode.XXX`, jamais un nombre/une chaîne littérale).
|
||||||
|
- **`packages/error-tools`** — package séparé, **indépendant de tout framework
|
||||||
|
HTTP** (n'importe pas `express`) : `HttpError`, `ErrorHandlerService`. Le mapping
|
||||||
|
« erreur → `{ status, body }` » n'a rien de spécifique à Express, donc il ne vit
|
||||||
|
pas dans `express-tools`.
|
||||||
- **`packages/express-tools`** — package séparé pour l'outillage Express générique
|
- **`packages/express-tools`** — package séparé pour l'outillage Express générique
|
||||||
(réutilisable par n'importe quel service Express du monorepo, pas seulement
|
(réutilisable par n'importe quel service Express du monorepo, pas seulement
|
||||||
`apps/api`) : `HttpError`, `ErrorHandlerService`, `createErrorMiddleware`.
|
`apps/api`) : `createErrorMiddleware` (adapte `ErrorHandlerService` à l'API
|
||||||
- **`apps/api`** — consomme `express-tools` : lève des `HttpError`, le middleware
|
Express), `ExpressServer`, `wrapAsyncHandler`.
|
||||||
d'erreur final n'est qu'un appel à `createErrorMiddleware(errorHandlerService)`.
|
- **`apps/api`** — consomme les deux : lève des `HttpError` (`error-tools`), le
|
||||||
|
middleware d'erreur final n'est qu'un appel à
|
||||||
|
`createErrorMiddleware(errorHandlerService)` (`express-tools`).
|
||||||
- **`apps/web` → `ErrorMessageService`** — associe chaque `ErrorCode` à une clé de
|
- **`apps/web` → `ErrorMessageService`** — associe chaque `ErrorCode` à une clé de
|
||||||
traduction, résolue via **i18next** (fichiers de locale sous `src/locales/`).
|
traduction, résolue via **i18next** (fichiers de locale sous `src/locales/`).
|
||||||
Les composants n'écrivent jamais de texte d'erreur en dur.
|
Les composants n'écrivent jamais de texte d'erreur en dur.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
subgraph TOOLS["packages/express-tools"]
|
subgraph ERRTOOLS["packages/error-tools"]
|
||||||
HTTPERR["HttpError"]
|
HTTPERR["HttpError"]
|
||||||
EHS["ErrorHandlerService.handle()"]
|
EHS["ErrorHandlerService.handle()"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph TOOLS["packages/express-tools"]
|
||||||
MW["createErrorMiddleware()"]
|
MW["createErrorMiddleware()"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -55,6 +64,7 @@ flowchart LR
|
||||||
SHARED -. contrat .-> EMS
|
SHARED -. contrat .-> EMS
|
||||||
|
|
||||||
style SHARED fill:none,stroke:#888,stroke-width:1px
|
style SHARED fill:none,stroke:#888,stroke-width:1px
|
||||||
|
style ERRTOOLS fill:none,stroke:#888,stroke-width:1px
|
||||||
style TOOLS fill:none,stroke:#888,stroke-width:1px
|
style TOOLS fill:none,stroke:#888,stroke-width:1px
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -97,12 +107,7 @@ Pour ajouter un nouveau cas d'erreur :
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## `packages/express-tools` — les pièces liées aux erreurs
|
## `packages/error-tools` — les pièces liées aux erreurs, indépendantes du framework
|
||||||
|
|
||||||
`packages/express-tools` contient aussi `ExpressServer` (init serveur,
|
|
||||||
enregistrement de routes/middlewares) et `wrapAsyncHandler` — voir
|
|
||||||
[backend-architecture.md](./backend-architecture.md) pour le détail complet du
|
|
||||||
package. Les pièces qui concernent spécifiquement les erreurs :
|
|
||||||
|
|
||||||
- **`http-error.ts`** — `HttpError` : erreur typée portant `status` (code HTTP) et
|
- **`http-error.ts`** — `HttpError` : erreur typée portant `status` (code HTTP) et
|
||||||
`code` (`ErrorCode`). C'est ce que lèvent les routes/services au lieu de
|
`code` (`ErrorCode`). C'est ce que lèvent les routes/services au lieu de
|
||||||
|
|
@ -112,24 +117,37 @@ package. Les pièces qui concernent spécifiquement les erreurs :
|
||||||
d'autre) en `{ status, body }`. Le cas générique (`INTERNAL_ERROR`, 500) logue
|
d'autre) en `{ status, body }`. Le cas générique (`INTERNAL_ERROR`, 500) logue
|
||||||
l'erreur côté serveur sans jamais exposer de détail interne au client.
|
l'erreur côté serveur sans jamais exposer de détail interne au client.
|
||||||
**N'importe pas `express`** — c'est un service générique, indépendant du
|
**N'importe pas `express`** — c'est un service générique, indépendant du
|
||||||
framework HTTP, qui fonctionnerait à l'identique derrière Fastify ou autre.
|
framework HTTP, qui fonctionnerait à l'identique derrière Fastify ou autre. C'est
|
||||||
- **`error-middleware.ts`** — `createErrorMiddleware(service)` : construit le
|
précisément pour ça qu'il vit dans son propre package plutôt que dans
|
||||||
middleware d'erreur Express (signature à 4 arguments) à partir du service —
|
`express-tools` : rien ici ne dépend d'Express, donc rien ici n'a sa place dans
|
||||||
c'est LUI la vraie couche Express, `ErrorHandlerService` reste agnostique.
|
un package *express*-tools.
|
||||||
|
|
||||||
Build réel (`tsc` → `dist/`, comme `packages/shared`) : consommé en JS compilé,
|
Build réel (`tsc` → `dist/`, comme `packages/shared`) : consommé en JS compilé,
|
||||||
pas en TS brut — voir la note dans
|
pas en TS brut — voir la note dans
|
||||||
[frontend-architecture.md](./frontend-architecture.md#note-sur-les-fichiers-dts)
|
[frontend-architecture.md](./frontend-architecture.md#note-sur-les-fichiers-dts)
|
||||||
sur pourquoi ça compte pour un runtime Node pur (Docker).
|
sur pourquoi ça compte pour un runtime Node pur (Docker).
|
||||||
|
|
||||||
|
## `packages/express-tools` — l'adaptateur Express
|
||||||
|
|
||||||
|
`packages/express-tools` contient `ExpressServer` (init serveur, enregistrement
|
||||||
|
de routes/middlewares) et `wrapAsyncHandler` — voir
|
||||||
|
[backend-architecture.md](./backend-architecture.md) pour le détail complet du
|
||||||
|
package. La pièce qui concerne spécifiquement les erreurs :
|
||||||
|
|
||||||
|
- **`error-middleware.ts`** — `createErrorMiddleware(service: ErrorHandlerService)` :
|
||||||
|
construit le middleware d'erreur Express (signature à 4 arguments) à partir
|
||||||
|
d'un `ErrorHandlerService` importé de `@batch-cooking/error-tools` — c'est LUI
|
||||||
|
la vraie couche Express, `ErrorHandlerService` reste agnostique. `express-tools`
|
||||||
|
dépend de `error-tools`, jamais l'inverse.
|
||||||
|
|
||||||
## Côté API (`apps/api`)
|
## Côté API (`apps/api`)
|
||||||
|
|
||||||
- **`app.ts`** — le middleware d'erreur final est enregistré via
|
- **`app.ts`** — le middleware d'erreur final est enregistré via
|
||||||
`server.setErrorHandler(createErrorMiddleware(errorHandlerService))` (voir
|
`server.setErrorHandler(createErrorMiddleware(errorHandlerService))` (voir
|
||||||
[backend-architecture.md](./backend-architecture.md) pour `ExpressServer`) ;
|
[backend-architecture.md](./backend-architecture.md) pour `ExpressServer`) ;
|
||||||
aucune logique de mapping n'y vit directement, tout est dans `express-tools`.
|
aucune logique de mapping n'y vit directement, tout est dans `error-tools`.
|
||||||
- Les modules métier (`modules/auth/auth.service.ts`, `middlewares/require-auth.ts`)
|
- Les modules métier (`modules/auth/auth.service.ts`, `middlewares/require-auth.ts`)
|
||||||
importent `HttpError` depuis `@batch-cooking/express-tools` et `ErrorCode` depuis
|
importent `HttpError` depuis `@batch-cooking/error-tools` et `ErrorCode` depuis
|
||||||
`@batch-cooking/shared`.
|
`@batch-cooking/shared`.
|
||||||
|
|
||||||
## Côté Web (`apps/web`)
|
## Côté Web (`apps/web`)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue