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:
Nicolas 2026-08-16 18:21:09 +02:00
parent 3a5dc83bf9
commit 95bd8fab87
17 changed files with 153 additions and 61 deletions

View file

@ -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).

View file

@ -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

View file

@ -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",

View file

@ -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));

View file

@ -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";

View file

@ -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";

View 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"
}
}

View file

@ -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

View file

@ -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). */

View 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";

View file

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"types": ["node"]
},
"include": ["src"]
}

View file

@ -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"
} }
} }

View file

@ -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 = (

View file

@ -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";

View file

@ -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

View file

@ -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.
--- ---

View file

@ -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`)