Product Attributes Module
Overview
The product-attributes module manages the many-to-many association between products and attribute definitions. Each record stores a value linking a specific product to an attribute (e.g., "Color: Red", "Material: Cotton").
Product attributes are business-scoped: every entry belongs to a specific business and is isolated from other businesses.
Architecture
The module follows Hexagonal Architecture with strict layer boundaries.
product-attributes/
├── product-attributes.module.ts # NestJS module wiring
├── domain/
│ └── product-attributes-repository.domain.ts # IProductAttributesRepository port + injection token
├── application/
│ └── product-attributes.service.ts # Use cases (CRUD + pagination)
├── infrastructure/
│ └── product-attributes.repository.ts # Kysely DB adapter (implements port)
└── interfaces/
├── product-attributes.controller.ts # HTTP adapter
├── dtos/
│ ├── create-product-attribute.dto.ts
│ └── update-product-attribute.dto.ts
└── query/
└── paginate-product-attributes.query.ts
Layer responsibilities
| Layer | Responsibility |
|---|---|
domain/ | Repository port (IProductAttributesRepository) and PRODUCT_ATTRIBUTES_REPOSITORY injection token. |
application/ | ProductAttributesService — orchestrates CRUD operations and pagination. |
infrastructure/ | ProductAttributesRepository — Kysely queries against the product_attribute table. Implements IProductAttributesRepository. |
interfaces/ | ProductAttributesController — maps HTTP requests to service calls. Thin layer with no business logic. |
Dependency rule
interfaces → application → domain ← infrastructure
Domain Concepts
Product Attribute
An association record that assigns a value from an attribute definition to a specific product.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key (auto-generated) |
productId | UUID | FK to product |
attributeId | UUID | FK to attribute |
value | string | The attribute value (e.g., "Red", "Large") |
businessId | UUID | FK to business |
isActive | boolean | Defaults to true |
createdBy | UUID | User who created |
updatedBy | UUID | User who last updated (nullable) |
createdAt | timestamptz | Auto-set on creation |
updatedAt | timestamptz | Set on update |
Access Control
All endpoints require Bearer token authentication and are scoped to PolicyResource.ProductAttribute for RBAC.
| Action | Permission |
|---|---|
| Create | PolicyAction.Create |
| List | PolicyAction.Read |
| Get by ID | PolicyAction.Read |
| Update | PolicyAction.Update |
| Delete | PolicyAction.Delete |
API Endpoints
POST /product-attributes
Create a new product attribute.
Body:
{
"productId": "<uuid>",
"attributeId": "<uuid>",
"value": "Red",
"businessId": "<uuid>",
"isActive": true,
"createdBy": "<uuid>"
}
Responses: 201 Created | 400 Invalid body | 401 Unauthorized
GET /product-attributes
List product attributes with pagination, search, and sorting.
Query parameters:
| Param | Required | Description |
|---|---|---|
businessId | No | UUID filter by business |
productId | No | UUID filter by product |
page | No | Page number (default: 1) |
size | No | Page size (default: 10; 0 = no limit) |
search | No | Case-insensitive search on value, product name, attribute name |
orderBy | No | Sort field (value) |
order | No | asc or desc |
Responses: 200 Paginated list | 401 Unauthorized
GET /product-attributes/:id
Get a single product attribute by UUID.
Query parameters: businessId (required)
Responses: 200 Found | 404 Not found | 401 Unauthorized
PATCH /product-attributes/:id
Partially update a product attribute.
Body:
{
"value": "Blue",
"businessId": "<uuid>",
"updatedBy": "<uuid>"
}
Responses: 200 Updated | 400 Invalid body | 404 Not found | 401 Unauthorized
DELETE /product-attributes/:id
Delete a product attribute.
Query parameters: businessId (required)
Responses: 200 Deleted | 404 Not found | 401 Unauthorized
Related Modules
- Products — products own product attribute records via
productIdFK - Attributes — attribute definitions referenced via
attributeIdFK - Product Variants — variant option types/values are a separate system; product attributes store additional metadata