typeorm-dev
TypeORM Dev
Develop TypeORM entities, migrations, and repository patterns following Sellernote conventions.
Convention Loading
Before starting any work, Read the relevant reference files from references/ within this skill directory:
-
Always read first (core rules):
references/TYPEORM_CONVENTION.md- Entity, Relation, Migration, Transaction, Repository rulesreferences/DATABASE_CONVENTION.md- Common fields, naming, ID strategy, migration principles
-
Read when relevant:
references/MYSQL_CONVENTION.md- MySQL-specific types, ENUM prohibition, DECIMAL, timezone, indexreferences/REDIS_CONVENTION.md- When caching strategy involves Redis alongside DBreferences/COMMON_CONVENTION.md- When unsure about naming, git conventions, error codesreferences/TYPESCRIPT_CONVENTION.md- When unsure about TS style, imports, types, enum vs union
Workflow
Follow these steps sequentially. Skip a step only when it does not apply to the task.
Step 1: Explore Existing Code
- Identify the target feature module under
src/modules/ - Find the existing
BaseEntityabstract class (typically in a shared/common module) - Locate
DecimalTransformerif it exists; if not, create it incommon/transformers/ - Check
data-source.tsat project root for migration and entity configuration
Step 2: Define Domain Model Interface
Create modules/{feature}/interfaces/{feature}.model.interface.ts:
- [MUST] Include only data fields owned by this model (no relation fields)
- [MUST] Include
id,_no,createdAt,updatedAt,deletedAtfrom BaseEntity
If relations need typing, create a separate I{Feature}ModelRelation interface in {feature}-model-relation.interface.ts using Relation<> wrappers.
See references/TYPEORM_CONVENTION.md > "Domain Model Interface" for full examples.
Step 3: Create Entity
Place in modules/{feature}/entities/{feature}.entity.ts. One Entity per file.
Key requirements (Sellernote-specific):
- [MUST] Extend custom
BaseEntity(NOT TypeORM's built-inBaseEntity) - [MUST]
implements IXxxModel, IXxxModelRelation - [MUST]
@Entity('table_name')with explicit snake_case table name - [MUST]
@Column()with explicit database type on every column - [MUST] Nullable columns:
nullable: true+| nullin TS type
Step 4: Define Relations
Key Sellernote-specific relation rules:
Relation<> wrapper is mandatory -- prevents circular dependency issues:
// Every relation property must use Relation<> wrapper
user: Relation<User>; // NOT: user: User
items: Relation<OrderItem[]>; // NOT: items: OrderItem[]
Explicit FK columns alongside relations -- enables FK access without loading relation:
@Column({ type: 'char', length: 36 })
userId: string; // Explicit FK column
@ManyToOne(() => User, (user) => user.orders)
@JoinColumn({ name: 'user_id' }) // Explicit column name
user: Relation<User>;
ManyToMany -- explicit @JoinTable names:
@JoinTable({
name: 'post_tag',
joinColumn: { name: 'post_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'tag_id', referencedColumnName: 'id' },
})
tags: Relation<Tag[]>;
Additional rules: No eager: true, @Index() with explicit name (idx_{table}_{columns}).
See references/TYPEORM_CONVENTION.md > "Relation" and "Index" sections for full details.
Step 5: Generate Migration
# Generate from Entity changes
npx typeorm migration:generate -d ./data-source.ts src/migrations/DescriptiveName
# Manual migration (if needed)
npx typeorm migration:create src/migrations/DescriptiveName
# Run migration
npx typeorm migration:run -d ./data-source.ts
Rules: Both up() and down() required. No synchronize: true in production.
See references/TYPEORM_CONVENTION.md > "Migration" section for file examples.
Step 6: Verify Repository Usage
- [MUST] Repository API (
find,findOne,save) for simple CRUD - [MUST] QueryBuilder only for complex queries (GROUP BY, aggregates, subqueries)
- [MUST] Parameterized queries (
:paramNamesyntax) -- no string interpolation - [MUST NOT] Business logic, domain branching, or HttpExceptions in Repository
Sellernote-Specific Patterns
These are non-standard patterns specific to Sellernote. Standard TypeORM patterns are in the reference files.
Custom BaseEntity (not TypeORM's built-in)
Every Entity MUST extend the custom BaseEntity providing: id (UUID PK), _no (BIGINT AUTO_INCREMENT UNIQUE), createdAt, updatedAt, deletedAt.
// common/entities/base.entity.ts
export abstract class BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'bigint', unique: true })
@Generated('increment')
_no: number;
@CreateDateColumn({ type: 'datetime' })
createdAt: Date;
@UpdateDateColumn({ type: 'datetime' })
updatedAt: Date;
@DeleteDateColumn({ type: 'datetime', nullable: true })
deletedAt: Date | null;
}
MUST NOT import BaseEntity from typeorm (Active Record pattern is forbidden).
DecimalTransformer
All decimal/money columns MUST use DecimalTransformer to convert MySQL's string return to number. Without it, order.totalAmount + 1000 becomes "15000.001000" (string concatenation).
@Column({
type: 'decimal', precision: 15, scale: 2,
transformer: new DecimalTransformer(),
})
totalAmount: number;
See references/TYPEORM_CONVENTION.md > "Custom Transformer" for the full DecimalTransformer implementation.
Enum as VARCHAR (not MySQL ENUM)
Store enum values as VARCHAR, never type: 'enum' (MySQL ENUM requires ALTER TABLE to add values).
@Column({ type: 'varchar', length: 20, default: OrderStatus.PENDING })
status: OrderStatus; // OrderStatus is a string-based TS enum
Transaction: @Transactional() only
Use @Transactional() from typeorm-transactional. MUST NOT use QueryRunner-based manual transactions.
import { Transactional } from 'typeorm-transactional';
@Transactional()
async createOrder(dto: CreateOrderDto): Promise<Order> {
// Use normal repositories -- transaction propagates via Async Local Storage
const order = await this.orderRepository.save({ ...dto });
await this.orderItemRepository.save(dto.items.map(item => ({ orderId: order.id, ...item })));
return order;
}
See references/TYPEORM_CONVENTION.md > "Transaction" section for setup (initializeTransactionalContext, addTransactionalDataSource) and propagation options.
File Structure
src/
common/
entities/base.entity.ts # Custom BaseEntity
transformers/decimal.transformer.ts # DecimalTransformer
modules/{feature}/
interfaces/
{feature}.model.interface.ts # IXxxModel (data fields only)
{feature}-model-relation.interface.ts # IXxxModelRelation
entities/{feature}.entity.ts # Entity class
enums/{feature}-status.enum.ts # String-based enums
migrations/
{timestamp}-{DescriptiveName}.ts
data-source.ts # CLI DataSource config (project root)
Cross-Skill References
- API/Service/Controller work: Use the
nestjs-api-devskill for DTOs, Controllers, Services, Mappers, and NestJS module wiring