Create Backend Module
Reference: category module — full CRUD + files + soft delete + audit logs.
Checklist
[ ] 1. Prisma model + migrate
[ ] 2. src/modules/{feature}/ — module, controller, service, dto/
[ ] 3. Register in app.module.ts
[ ] 4. DTOs with global decorators
[ ] 5. Controller endpoints + guards
[ ] 6. Service methods with try/catch
[ ] 7. File upload (if needed) — see @backend-nest/file-upload
[ ] 8. Auth per endpoint — see @backend-nest/auth
Step 1 — Prisma
model product_tag {
id String @id @default(uuid())
name_en String
name_ar String
slug String @unique
imageUrl String?
sort Int @default(0)
deleted DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([deleted])
}
npx prisma migrate dev --name add-product-tag
Step 2 — Files
src/modules/product-tag/
├── product-tag.module.ts
├── product-tag.controller.ts
├── product-tag.service.ts
└── dto/
├── index.ts
├── create-product-tag.dto.ts
├── update-product-tag.dto.ts
└── get-all-product-tags.dto.ts
Step 3 — Standard Endpoints
| Route | Guard | Notes |
|---|---|---|
GET get-all-{features} |
Public | pagination + search + filters |
GET get-{feature} |
OptionalJwtGuard | ?id= or ?slug= |
POST create-{feature} |
Admin+Editor | multipart if files |
PATCH update-{feature} |
Admin+Editor | ?id= |
DELETE delete-{feature} |
Admin+Editor | soft → hard on second call |
PATCH update-{feature}-sort |
Admin+Editor | if sortable |
Step 4 — Controller
@Get('get-all-product-tags')
async getAll(@Query() pagination: GlobalPaginationDto, @Query() search: GlobalSearchDto, @Query() dto: GetAllDto) {
const data = await this.service.getAll(pagination, search, dto);
return sendCustomResponse({ data: data.requests, count: data.count });
}
@UseGuards(JwtGuard, AuthorizeCoreUsersGuard)
@ApiBearerAuth()
@CoreUserType([CoreUserEnum.ADMIN, CoreUserEnum.EDITOR])
@Post('create-product-tag')
@HttpCode(200)
async create(@Body() dto: CreateDto, @GetUser() user: user) {
return sendSuccessfulResponse(await this.service.create(dto, user));
}
Step 5 — Service getAll
async getAll(pagination, search, dto) {
try {
const where: Prisma.product_tagWhereInput = {};
if (search?.search) {
where.OR = [
{ name_en: { contains: search.search, mode: 'insensitive' } },
{ name_ar: { contains: search.search, mode: 'insensitive' } },
];
}
const [count, requests] = await Promise.all([
this.prisma.product_tag.count({ where }),
this.prisma.product_tag.findMany({ where, ...checkPagination(pagination), orderBy: { sort: 'asc' } }),
]);
return { count, requests };
} catch (error) { handleException(error, false, {}); }
}
Step 6 — Delete (soft then hard)
if (existing.deleted) {
await this.prisma.junction.deleteMany({ where: { tagId: id } });
return this.prisma.product_tag.delete({ where: { id } });
}
return this.prisma.product_tag.update({ where: { id }, data: { deleted: new Date() } });
Step 7 — Module
@Module({
imports: [LogModule, StorageModule], // if needed
controllers: [ProductTagController],
providers: [ProductTagService, PerformanceTrackerService],
})
export class ProductTagModule {}
Complexity Guide
| Type | Extra |
|---|---|
| Simple CRUD | 5 endpoints |
| + files | Two-phase upload, @backend-nest/file-upload |
| + relations | Junction tables, complex includes |
| + sort | update-X-sort endpoint |
| + audit | LogService.createLogSafely() |
Templates: templates.md