← スキル一覧に戻る

api-conventions
by feraudet
Consultant management system for service company (SSII/ESN)
⭐ 0🍴 0📅 2026年1月15日
SKILL.md
name: api-conventions description: Standards pour la conception d'API REST. Use when "api", "endpoint", "route", "rest api", "http". allowed-tools: Read, Grep, Glob
API Conventions
Purpose
Standards pour développer les API REST dans le projet consultant-manager.
Principes REST
Ressources
- Consultant:
/api/consultants - Mission:
/api/missions - Dashboard:
/api/dashboard
Méthodes HTTP
GET- 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 ressource
URLs
GET /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é
Status Codes HTTP
Success
200 OK- GET, PUT réussis201 Created- POST réussi204 No Content- DELETE réussi
Client Errors
400 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ée
Server Errors
500 Internal Server Error- Erreur serveur
Format de Réponse
Success Response
// GET /api/consultants/:id
{
"id": "uuid",
"nom": "Dupont",
"prenom": "Jean",
"email": "jean@example.com",
"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 Response
{
"error": "Message d'erreur lisible",
"details": [
{
"field": "email",
"message": "Email invalide"
}
]
}
Liste Paginée
{
"data": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 150,
"totalPages": 8
}
}
Query Parameters
Filtres
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
Tri
GET /api/consultants?sortBy=nom&order=asc
GET /api/missions?sortBy=dateDebut&order=desc
Pagination
GET /api/consultants?page=2&pageSize=20
Include Relations
GET /api/consultants/:id?include=missions
Validation
Backend avec Zod
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' });
}
};
Gestion d'Erreurs
Error Handler Middleware
// 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
});
});
Try-Catch Pattern
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' });
}
};
Dates
Format ISO 8601
{
"dateDebut": "2026-01-15T00:00:00.000Z",
"dateFin": "2026-06-30T23:59:59.999Z"
}
Parsing
// 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'
});
CORS
Configuration
import cors from 'cors';
// Development: permissif
app.use(cors());
// Production: restrictif
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
credentials: true
}));
Sécurité
Validation Stricte
// ✅ Valider toutes les entrées
const validatedData = schema.parse(req.body);
// ❌ Utiliser directement req.body
await prisma.consultant.create({ data: req.body });
Rate Limiting
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);
Helmet (Headers de sécurité)
import helmet from 'helmet';
app.use(helmet());
Documentation
JSDoc sur Routes
/**
* 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) => {
// ...
};
Swagger/OpenAPI (Futur)
paths:
/api/consultants:
get:
summary: Liste des consultants
parameters:
- name: statut
in: query
schema:
type: string
enum: [DISPONIBLE, EN_MISSION, EN_CONGES, INDISPONIBLE]
responses:
200:
description: Liste des consultants
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Consultant'
Patterns Spécifiques au Projet
Calculs Côté Backend
// ✅ Calculs métier dans l'API
export const getMission = async (req: Request, res: Response) => {
const mission = await prisma.mission.findUnique({ where: { id } });
res.json({
...mission,
revenusGeneres: calculateRevenue(mission.tjm, mission.dateDebut, mission.dateFin),
dureeJours: calculateDuration(mission.dateDebut, mission.dateFin)
});
};
// ❌ Laisser le calcul au frontend
// Frontend ne devrait PAS calculer les revenus
JSON Fields
// Consultant.competences est stocké en JSON string
export const getConsultant = async (req: Request, res: Response) => {
const consultant = await prisma.consultant.findUnique({ where: { id } });
res.json({
...consultant,
competences: JSON.parse(consultant.competences) // Convertir en array
});
};
Statut Calculé
// Calculer le statut réel basé sur missions actives
export const getAllConsultants = async (req: Request, res: Response) => {
const consultants = await prisma.consultant.findMany({
include: { missions: true }
});
const consultantsWithStatus = consultants.map(consultant => ({
...consultant,
statut: calculateConsultantStatus(consultant) // Fonction utilitaire
}));
res.json(consultantsWithStatus);
};
Testing API
Avec curl
# GET
curl http://localhost:3000/api/consultants
# POST
curl -X POST http://localhost:3000/api/consultants \
-H "Content-Type: application/json" \
-d '{
"nom": "Dupont",
"prenom": "Jean",
"email": "jean@example.com",
"competences": ["React"],
"tjm": 500
}'
# DELETE
curl -X DELETE http://localhost:3000/api/consultants/uuid
Avec Postman/Insomnia
Créer une collection avec tous les endpoints du projet.
Tests d'Intégration
import request from 'supertest';
import { app } from '../src/index';
describe('Consultants API', () => {
it('should create a consultant', async () => {
const response = await request(app)
.post('/api/consultants')
.send({
nom: 'Test',
prenom: 'User',
email: 'test@example.com',
competences: ['React'],
tjm: 500
})
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.email).toBe('test@example.com');
});
});
Checklist API
Avant de commiter une nouvelle API:
- Routes RESTful (GET, POST, PUT, DELETE)
- Validation Zod sur toutes les entrées
- Status codes HTTP appropriés
- Gestion d'erreurs avec try-catch
- Réponses JSON consistantes
- CORS configuré
- Dates en ISO 8601
- Calculs métier côté backend
- Documentation JSDoc
- Tests d'intégration
- Pas de données sensibles dans les logs
Ressources
スコア
総合スコア
50/100
リポジトリの品質指標に基づく評価
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
レビュー
💬
レビュー機能は近日公開予定です