@cidevstudio/nest-common
v1.0.0
Published
Socle CIDevStudio pour APIs NestJS : BaseEntity, BaseService, guards JWT/Roles/Permissions, filters, interceptors, pipes, config multi-DB/CORS/Swagger, upload Multer, logging Winston et utilitaires réutilisables.
Downloads
75
Maintainers
Readme
@cidevstudio/nest-common
Socle CIDevStudio pour APIs NestJS — briques réutilisables extraites du skill
expert-nestjs-backend-api.
Fournit tout ce qui est transversal et générique dans une API NestJS CIDevStudio : BaseEntity, BaseService, guards JWT/Roles/Permissions, filters, interceptors, pipes de validation, décorateurs, config multi-DB/CORS/Swagger/port dynamique, base de stratégie JWT, upload Multer, logging Winston, utilitaires FCFA.
Aucun code métier, aucun secret, aucune config figée : chaque projet consomme et configure.
Installation
npm install @cidevstudio/nest-commonLe package déclare NestJS, TypeORM, class-validator, class-transformer, bcrypt, rxjs et reflect-metadata comme peerDependencies — ils doivent déjà être installés dans ton projet (c'est le cas de tout projet créé avec nest new). @nestjs/config, @nestjs/typeorm, @nestjs/passport, @nestjs/swagger, bcrypt, passport-jwt, multer, winston, nest-winston et winston-daily-rotate-file sont des peer dependencies optionnelles : installe-les seulement si tu utilises les fonctionnalités correspondantes (config/, strategies/, guards, upload/, logging/, etc.).
Contenu
Base
import { BaseEntity, BaseService } from '@cidevstudio/nest-common';
// Entité
@Entity()
export class Product extends BaseEntity {
@Column() name: string;
@Column({ type: 'decimal', precision: 12, scale: 2 }) price: number;
}
// Service
@Injectable()
export class ProductsService extends BaseService<Product> {
protected readonly logger = new Logger(ProductsService.name);
constructor(@InjectRepository(Product) repo: Repository<Product>) {
super(repo);
}
// hérite gratuitement de findAll(page, limit), findOne(id), remove(id)
}DTOs
import { PaginationDto, ApiResponseDto } from '@cidevstudio/nest-common';
export class ListProductsDto extends PaginationDto {
@IsOptional() @IsString() search?: string;
}Décorateurs
import { Public, Roles, CurrentUser, Permissions } from '@cidevstudio/nest-common';
@Controller('products')
export class ProductsController {
@Public()
@Get('featured')
featured() { /* accessible sans JWT */ }
@Roles('ADMIN', 'MANAGER')
@Post()
create(@CurrentUser() user: RequestUser) { /* ... */ }
@Permissions('product.delete')
@Delete(':id')
remove(@CurrentUser('id') userId: number) { /* ... */ }
}Guards
// app.module.ts
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard, RolesGuard, PermissionsGuard } from '@cidevstudio/nest-common';
@Module({
providers: [
{ provide: APP_GUARD, useClass: JwtAuthGuard },
{ provide: APP_GUARD, useClass: RolesGuard },
{ provide: APP_GUARD, useClass: PermissionsGuard },
],
})
export class AppModule {}Prérequis : ton projet doit déclarer une
JwtStrategynommée'jwt'(Passport) — voir section Stratégie JWT ci-dessous.
Config — multi-DB, CORS, Swagger, port dynamique
// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { getDatabaseConfig } from '@cidevstudio/nest-common';
import { ENTITIES } from './entities';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => getDatabaseConfig(configService, ENTITIES),
}),
],
})
export class AppModule {}// main.ts
import { getCorsConfig, setupSwagger, getPreferredPort, getMaxPortAttempts, listenOnAvailablePort } from '@cidevstudio/nest-common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
app.enableCors(getCorsConfig(configService)); // env CORS_ORIGIN (défaut '*')
setupSwagger(app, configService); // UI /v1/docs — JSON /v1/api-json
const port = await listenOnAvailablePort(
app,
getPreferredPort(configService), // env PORT (défaut 3001)
getMaxPortAttempts(configService), // env PORT_MAX_ATTEMPTS (défaut 50)
);
}getDatabaseConfig supporte SQLite (dev), PostgreSQL, MySQL/MariaDB via DB_TYPE /
DB_TYPE_PROD + les variables DB_HOST/DB_PORT/DB_USERNAME/DB_PASSWORD/DB_NAME (ou DB_PATH pour
SQLite). listenOnAvailablePort évite le crash EADDRINUSE en réessayant PORT+1, PORT+2, ...
Stratégie JWT
JwtStrategyBase fournit la mécanique Passport (extraction du Bearer token, vérification de
JWT_SECRET) — chaque projet fournit uniquement la recherche de l'utilisateur en base :
// common/strategies/jwt.strategy.ts (reste local à chaque projet)
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { JwtStrategyBase, RequestUser } from '@cidevstudio/nest-common';
import { User } from '../../users/entities/user.entity';
@Injectable()
export class JwtStrategy extends JwtStrategyBase {
constructor(
configService: ConfigService,
@InjectRepository(User) private readonly usersRepository: Repository<User>,
) {
super(configService);
}
protected async resolveUser(payload: { sub: number }): Promise<RequestUser | null> {
const user = await this.usersRepository.findOne({ where: { id: payload.sub } });
if (!user) return null;
return { id: user.id, email: user.email, role: user.role };
}
}Upload — Multer
import { FileInterceptor } from '@nestjs/platform-express';
import { createMulterConfig, UploadService, MulterFile } from '@cidevstudio/nest-common';
@Controller('media')
export class MediaController {
constructor(private readonly uploadService: UploadService) {}
@Post('upload/image')
@UseInterceptors(FileInterceptor('file', createMulterConfig('images')))
upload(@UploadedFile() file: MulterFile) {
return { fileUrl: this.uploadService.getFileUrl(file.filename, 'images') };
}
}Racine de stockage configurable via UPLOAD_DEST (défaut ./public/uploads), limites via
UPLOAD_MAX_SIZE / UPLOAD_MAX_FILES.
Logging — Winston
// app.module.ts
import { AppLoggerModule } from '@cidevstudio/nest-common';
@Module({ imports: [AppLoggerModule, /* ... */] })
export class AppModule {}// main.ts
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
}Console colorée en dev + fichiers rotatifs quotidiens (logs/error-%DATE%.log,
logs/combined-%DATE%.log), configurables via LOG_DIR, LOG_LEVEL, LOG_MAX_FILES,
LOG_MAX_SIZE.
Filters & Interceptors globaux
// main.ts
import { HttpExceptionFilter, ResponseInterceptor, CustomValidationPipe } from '@cidevstudio/nest-common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new CustomValidationPipe());
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalInterceptors(new ResponseInterceptor());
await app.listen(3000);
}Utilitaires
import { HashUtil, MoneyUtil, DateUtil, ResponseUtil } from '@cidevstudio/nest-common';
// Auth
const hash = await HashUtil.hash('Admin@123!');
const ok = await HashUtil.compare('Admin@123!', hash);
// FCFA
MoneyUtil.formatFCFA(1_250_000); // "1 250 000 FCFA"
MoneyUtil.toTTC(10_000); // 11800 (TVA 18%)
MoneyUtil.toTTC(10_000, MoneyUtil.TVA_REDUITE); // 10900 (TVA 9%)
// Dates fr-CI
DateUtil.formatFr(new Date()); // "15/09/2026"
DateUtil.addDays(new Date(), 30);Format de réponse standard
Toutes les réponses sont automatiquement enveloppées par ResponseInterceptor :
{
"success": true,
"message": "Opération réussie",
"data": { ... },
"timestamp": "2026-09-15T12:00:00.000Z"
}Les erreurs par HttpExceptionFilter :
{
"success": false,
"statusCode": 404,
"message": "Entité ID 42 introuvable",
"path": "/v1/products/42",
"method": "GET",
"timestamp": "2026-09-15T12:00:00.000Z"
}Conventions respectées
- ID auto-incrémentés (INT) — jamais d'UUID
type: 'datetime'— compatible SQLite / MySQL / PostgreSQL- Soft delete via
@DeleteDateColumnsurBaseEntity - Commentaires et messages d'erreur en français
- FCFA / XOF pour les montants (TVA 18% et 9%)
- Whitelist strict sur les DTOs (aucun champ inconnu accepté)
Build & publication
npm install
npm run build
npm publish # provenance activée automatiquementLicence
MIT © Beyra Jean Didier (CIDevStudio)
