Bonnes Pratiques - Endpoints API
🚀 Structure des Contrôleurs
Séparation Public/Privé
// Public : /api/publique/resource
@ApiTags('Resource (Public)')
@Controller('api/publique/resource')
// Privé : /api/privee/resource
@ApiTags('Resource (Private)')
@Controller('api/privee/resource')
Répartition des responsabilités
- Public : Les routes accessibles par toutes les apps autorisées
- Privé : Les routes utilisées uniquement en interne
📝 Décorateurs Essentiels
Documentation OpenAPI
@ApiOperation({
summary: 'Action concise',
description: 'Description détaillée de l\'action',
})
@ApiResponse({ status: 201, description: 'Succès' })
@ApiResponse({ status: 409, description: 'Conflit' })
Validation & HTTP
@Post()
@HttpCode(HttpStatus.CREATED)
@UsePipes(ValidationPipe)
async create(@Body() dto: CreateDto) {}
🔍 Logging Standard
private readonly logger = new Logger(`[FIDELITE][${ClassName.name}]`);
this.logger.log(`${METHOD} ${endpoint} - ${action} ${details}`);
✅ DTOs et Validation
export class CreateDto {
@IsString()
@MinLength(2)
@MaxLength(50)
name: string;
@IsOptional()
@IsEmail()
email?: string;
}
// Update = PartialType du Create
export class UpdateDto extends PartialType(CreateDto) {}
🗄️ Service Pattern
@Injectable()
export class Service {
async create(dto: CreateDto) {
// Vérifier les doublons → ConflictException
// Logger l'action
// Retourner le résultat
}
async findAll(options: PaginationOptions) {
// Pagination + filtrage
// Exclure les deletedAt
// Retourner { data, total, page, limit }
}
async remove(id: number) {
// Soft delete avec deletedAt
// Vérifier existence → NotFoundException
}
}
📊 Pagination Standard
@Get()
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'limit', required: false, type: Number })
async findAll(
@Query('page') page: number = 1,
@Query('limit') limit: number = 10,
) {
return this.service.findAll({ page, limit });
}
⚠️ Gestion d'Erreurs
// Service
if (existing) {
throw new ConflictException('Ressource déjà existante');
}
if (!found) {
throw new NotFoundException('Ressource non trouvée');
}
// Codes HTTP
// 201: POST création
// 200: GET, PATCH
// 204: DELETE
// 409: Conflit
// 404: Non trouvé
🏗️ Schema DB (Drizzle)
export const table = pgTable('table_name', {
id: bigserial('id', { mode: 'number' }).primaryKey(),
createdAt: timestamp('createdAt').notNull().defaultNow(),
updatedAt: timestamp('updatedAt').notNull().defaultNow(),
deletedAt: timestamp('deletedAt'), // Soft delete
// ... autres champs
});
📦 Module Standard
@Module({
imports: [DrizzleModule],
controllers: [ResourcePublicController, ResourcePrivateController],
providers: [ResourceService],
exports: [ResourceService],
})
export class ResourceModule {}
🎯 Checklist Rapide
- Séparation public/privé
- Décorateurs OpenAPI complets
- Validation des DTOs
- Logging sur chaque action
- Pagination pour les listes
- Soft delete uniquement
- Gestion d'erreurs appropriée
- Tests unitaires
- Schema DB avec timestamps