Aller au contenu principal

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