Standards pour la conception d'API REST. Use when "api", "endpoint", "route", "rest api", "http".
Standards pour développer les API REST dans le projet consultant-manager.
/api/consultants/api/missions/api/dashboardGET - Récupérer des données (lecture)POST - Créer une nouvelle ressourcePUT - Modifier une ressource existante (remplacer)PATCH - Modifier partiellement une ressourceDELETE - Supprimer une ressourceGET /api/consultants # Liste
GET /api/consultants/:id # Détail
POST /api/consultants # Créer
PUT /api/consultants/:id # Modifier
DELETE /api/consultants/:id # Supprimer
GET /api/missions
GET /api/missions/:id
POST /api/missions
PUT /api/missions/:id
DELETE /api/missions/:id
GET /api/missions/timeline # Endpoint spécial
GET /api/dashboard/stats # Endpoint agrégé
200 OK - GET, PUT réussis201 Created - POST réussi204 No Content - DELETE réussi400 Bad Request - Données invalides401 Unauthorized - Non authentifié403 Forbidden - Non autorisé404 Not Found - Ressource introuvable409 Conflict - Conflit (ex: email déjà utilisé)422 Unprocessable Entity - Validation échouée500 Internal Server Error - Erreur serveur// GET /api/consultants/:id
{
"id": "uuid",
"nom": "Dupont",
"prenom": "Jean",
"email": "[email protected]",
"telephone": "+33612345678",
"competences": ["React", "TypeScript"],
"tjm": 500,
"statut": "EN_MISSION",
"dateCreation": "2026-01-15T10:00:00.000Z",
"dateModification": "2026-01-15T10:00:00.000Z"
}
{
"error": "Message d'erreur lisible",
"details": [
{
"field": "email",
"message": "Email invalide"
}
]
}
{
"data": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 150,
"totalPages": 8
}
}
GET /api/consultants?statut=DISPONIBLE
GET /api/consultants?search=dupont
GET /api/missions?consultantId=uuid
GET /api/missions?dateDebut=2026-01-01&dateFin=2026-12-31
GET /api/consultants?sortBy=nom&order=asc
GET /api/missions?sortBy=dateDebut&order=desc
GET /api/consultants?page=2&pageSize=20
GET /api/consultants/:id?include=missions
import { z } from 'zod';
const consultantSchema = z.object({
nom: z.string().min(1, 'Nom requis'),
prenom: z.string().min(1, 'Prénom requis'),
email: z.string().email('Email invalide'),
telephone: z.string().optional(),
competences: z.array(z.string()).min(1, 'Au moins une compétence'),
tjm: z.number().positive('TJM doit être positif'),
statut: z.enum(['DISPONIBLE', 'EN_MISSION', 'EN_CONGES', 'INDISPONIBLE']).optional()
});
export const createConsultant = async (req: Request, res: Response) => {
try {
const validatedData = consultantSchema.parse(req.body);
const consultant = await prisma.consultant.create({
data: {
...validatedData,
competences: JSON.stringify(validatedData.competences)
}
});
res.status(201).json(consultant);
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({
error: 'Validation error',
details: error.errors
});
}
res.status(500).json({ error: 'Internal server error' });
}
};
// Express error handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
// Prisma errors
if (err.code === 'P2002') {
return res.status(409).json({
error: 'Unique constraint violation',
field: err.meta?.target
});
}
if (err.code === 'P2025') {
return res.status(404).json({
error: 'Resource not found'
});
}
// Zod validation errors
if (err instanceof z.ZodError) {
return res.status(400).json({
error: 'Validation error',
details: err.errors
});
}
// Generic error
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message
});
});
export const getConsultant = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const consultant = await prisma.consultant.findUnique({
where: { id },
include: { missions: true }
});
if (!consultant) {
return res.status(404).json({ error: 'Consultant not found' });
}
res.json(consultant);
} catch (error) {
console.error('Error fetching consultant:', error);
res.status(500).json({ error: 'Failed to fetch consultant' });
}
};
{
"dateDebut": "2026-01-15T00:00:00.000Z",
"dateFin": "2026-06-30T23:59:59.999Z"
}
// Backend: convertir string en Date
const dateDebut = new Date(req.body.dateDebut);
// Valider avec Zod
const missionSchema = z.object({
dateDebut: z.string().datetime(),
dateFin: z.string().datetime()
}).refine(data => {
return new Date(data.dateFin) > new Date(data.dateDebut);
}, {
message: 'dateFin must be after dateDebut'
});
import cors from 'cors';
// Development: permissif
app.use(cors());
// Production: restrictif
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true
}));
// ✅ Valider toutes les entrées
const validatedData = schema.parse(req.body);
// ❌ Utiliser directement req.body
await prisma.consultant.create({ data: req.body });
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);
import helmet from 'helmet';
app.use(helmet());
/**
* GET /api/consultants
*
* Liste tous les consultants avec filtres optionnels
*
* Query params:
* - statut (optional): DISPONIBLE | EN_MISSION | EN_CONGES | INDISPONIBLE
* - search (optional): Recherche par nom, prénom, email
*
* Returns: Consultant[]
*/
export const getAllConsultants = async (req: Request, res: Response) => {
// ...
};