Add Field to Existing Module
Not for new modules — use @backend-nest/create-module.
Reference: Find the target module in src/modules/{feature}/ and its Prisma model first.
Checklist
[ ] 1. Prisma — add field (+ relation/junction if needed) + migrate
[ ] 2. Create DTO — required/optional field decorator
[ ] 3. Update DTO — optional version of same field
[ ] 4. GetAll DTO — only if field is a list filter
[ ] 5. Service — create, update, getAll where, select/include
[ ] 6. Relations — connect/disconnect, junction cleanup on delete
[ ] 7. Search — add to where.OR if text-searchable
[ ] 8. Verify barrel export in dto/index.ts unchanged
Step 1 — Prisma
Add the field to the existing model in schema.prisma:
model product {
// ...existing fields
sku String? // new optional string
isFeatured Boolean @default(false)
typeId Int? // new FK
type type? @relation(fields: [typeId], references: [id])
}
| Field type | Prisma pattern |
|---|---|
| Optional string/number/boolean | String?, Int?, Boolean? |
| Required with default | Boolean @default(false), Int @default(0) |
| Enum | status ProductStatus @default(DRAFT) + enum ProductStatus { ... } |
| FK (many-to-one) | {name}Id Int? + relation field |
| Many-to-many | junction model e.g. product_category |
npx prisma migrate dev --name add-product-sku-and-featured
See @backend-nest/prisma-database for ID types, soft delete, indexes.
Step 2 — DTOs
Use global decorators — see @backend-nest/global-dtos.
Create DTO — add the new field:
export class CreateProductDto {
// ...existing fields
@OptionalStringField('Stock keeping unit') sku?: string;
@OptionalBooleanField() isFeatured?: boolean;
@OptionalNumberField() typeId?: number;
@OptionalEnumField(ProductStatus) status?: ProductStatus;
}
Update DTO — mirror create with optional decorators:
@OptionalStringField() sku?: string;
@OptionalBooleanField() isFeatured?: boolean;
@OptionalNumberField() typeId?: number;
GetAll DTO — only when the field is a filter on list endpoints:
export class GetAllProductsDto {
@OptionalBooleanField() isFeatured?: boolean;
@OptionalNumberField() typeId?: number;
@OptionalEnumField(ProductStatus) status?: ProductStatus;
}
Step 3 — Service
create
Spread scalar fields from DTO. Handle relations separately:
const created = await this.prisma.product.create({
data: {
...dto,
typeId: undefined, // strip FK before spread if using connect
type: dto.typeId ? { connect: { id: dto.typeId } } : undefined,
},
});
update
Build a data object — only include provided fields:
const data: Prisma.productUpdateInput = {};
if (dto.sku !== undefined) data.sku = dto.sku;
if (dto.isFeatured !== undefined) data.isFeatured = dto.isFeatured;
if (dto.typeId !== undefined) {
data.type = dto.typeId ? { connect: { id: dto.typeId } } : { disconnect: true };
}
await this.prisma.product.update({ where: { id }, data });
getAll — filter
if (dto.isFeatured !== undefined) where.isFeatured = dto.isFeatured;
if (dto.typeId) where.typeId = dto.typeId;
if (dto.status) where.status = dto.status;
getAll / getOne — return the field
Ensure findMany/findFirst selects or includes the new field (default unless select omits it). Add relation to include if needed:
include: { type: true }
search (text fields only)
where.OR = [
// ...existing
{ sku: { contains: search.search, mode: 'insensitive' } },
];
Step 4 — Relations
| Relation | Create | Update | Read | Delete cleanup |
|---|---|---|---|---|
| Many-to-one | { connect: { id } } |
connect / disconnect | include: { rel: true } |
usually none |
| One-to-many | on parent side only | — | include: { children: true } |
cascade or block |
| Many-to-many | junction create/connect |
replace junction rows | include junction + related | deleteMany on junction |
Many-to-many replace pattern:
await this.prisma.product_category.deleteMany({ where: { productId: id } });
await this.prisma.product_category.createMany({
data: dto.categoryIds.map((categoryId) => ({ productId: id, categoryId })),
});
Add junction fields to create/update DTOs: @RequiredArrayOfNumbersField() categoryIds: number[]
Step 5 — Special field types
| Type | Also update |
|---|---|
File (imageUrl) |
@backend-nest/file-upload — DTO file decorator, service two-phase upload |
Bilingual (name_en/name_ar) |
Both DTO fields + both in search OR |
| Sort | Usually no schema change; skip unless adding sort to new entity |
| Unique (slug, sku) | @unique in Prisma + duplicate check in create/update service |
Step 6 — Controller
Usually no change — existing endpoints accept expanded DTOs.
Add controller/query changes only when:
- New filter on getAll → already covered by GetAll DTO
- New dedicated endpoint (rare) — e.g. bulk update of the new field
Verification
[ ] Migration applied
[ ] Create with new field works
[ ] Update with new field works (and partial update omits undefined)
[ ] List filter works (if GetAll DTO added)
[ ] Relation connect/disconnect works
[ ] Swagger reflects new DTO fields