feat(backend): add global error handling and request logging middlewares

This commit is contained in:
SaidSoighiri94
2026-06-15 17:46:39 +02:00
parent 2aefe5cfbe
commit 2ac1a3281b
7 changed files with 100 additions and 8 deletions
+20 -7
View File
@@ -2,32 +2,45 @@ import express from 'express';
import cors from 'cors';
import { env } from './config/env';
import { connectDatabase, disconnectDatabase } from './db/prisma';
import { logger } from './utils/logger';
import { requestLogger } from './middlewares/request-logger';
import { notFoundHandler } from './middlewares/not-found';
import { errorHandler } from './middlewares/error-handler';
const app = express();
// Middlewares globaux
app.use(cors());
app.use(cors({ origin: env.frontendUrl }));
app.use(express.json());
app.use(requestLogger);
// Route de test
app.get('/health', (_req, res) => {
res.json({ status: 'ok', app: 'EME API' });
});
/* notFoundHandler et errorHandler doivent rester après toutes les routes :
le premier capture les URL non gérées, le second clôt la chaîne Express. */
app.use(notFoundHandler);
app.use(errorHandler);
async function bootstrap(): Promise<void> {
await connectDatabase();
const server = app.listen(env.port, () => {
console.log(`[EME API] Serveur demarre sur http://localhost:${env.port}`);
logger.info(`Serveur demarre sur http://localhost:${env.port}`);
});
process.on('SIGINT', async () => {
const shutdown = async (signal: string): Promise<void> => {
logger.info(`Arret en cours (${signal})`);
await disconnectDatabase();
server.close(() => process.exit(0));
});
};
// SIGTERM est le signal envoyé par Docker à l'arrêt du container.
process.on('SIGINT', () => void shutdown('SIGINT'));
process.on('SIGTERM', () => void shutdown('SIGTERM'));
}
bootstrap().catch((error: unknown) => {
console.error('[EME API] Echec du demarrage', error);
logger.error('Echec du demarrage', error);
process.exit(1);
});
+10
View File
@@ -0,0 +1,10 @@
export class AppError extends Error {
constructor(
public readonly statusCode: number,
message: string,
) {
super(message);
// Sans cette ligne, `instanceof AppError` échoue après compilation (cas d'une classe étendant Error).
Object.setPrototypeOf(this, AppError.prototype);
}
}
@@ -0,0 +1,25 @@
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../errors/app-error';
import { env } from '../config/env';
import { logger } from '../utils/logger';
/* Le 4e argument est requis par Express pour identifier un gestionnaire d'erreurs,
même s'il n'est pas utilisé. */
export function errorHandler(err: unknown, _req: Request, res: Response, _next: NextFunction): void {
const isAppError = err instanceof AppError;
const statusCode = isAppError ? err.statusCode : 500;
const message = isAppError ? err.message : 'Erreur interne du serveur';
if (!isAppError) {
logger.error('Erreur non geree', err);
}
const exposeStack = env.nodeEnv === 'development' && err instanceof Error && !isAppError;
res.status(statusCode).json({
error: {
message,
...(exposeStack ? { stack: err.stack } : {}),
},
});
}
+6
View File
@@ -0,0 +1,6 @@
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../errors/app-error';
export function notFoundHandler(req: Request, _res: Response, next: NextFunction): void {
next(new AppError(404, `Route introuvable: ${req.method} ${req.originalUrl}`));
}
@@ -0,0 +1,12 @@
import { Request, Response, NextFunction } from 'express';
import { logger } from '../utils/logger';
export function requestLogger(req: Request, res: Response, next: NextFunction): void {
const start = Date.now();
res.on('finish', () => {
logger.info(`${req.method} ${req.originalUrl} ${res.statusCode} ${Date.now() - start}ms`);
});
next();
}
+18
View File
@@ -0,0 +1,18 @@
type LogLevel = 'info' | 'warn' | 'error';
function log(level: LogLevel, message: string, meta?: unknown): void {
const line = `[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}`;
const write = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
if (meta !== undefined) {
write(line, meta);
} else {
write(line);
}
}
export const logger = {
info: (message: string, meta?: unknown): void => log('info', message, meta),
warn: (message: string, meta?: unknown): void => log('warn', message, meta),
error: (message: string, meta?: unknown): void => log('error', message, meta),
};