nestjs-api-dev
NestJS API Dev
Develop NestJS APIs following Sellernote's 3-layer architecture and convention documents.
Convention Loading
Before starting any work, Read the relevant reference files from references/ within this skill directory:
-
Always read first (core rules):
references/BACKEND_CONVENTION.md- 3-layer architecture, DTO/Entity namingreferences/BACKEND_ARCHITECTURE_CONVENTION.md- Layer responsibilities, forbidden patternsreferences/NESTJS_CONVENTION.md- NestJS-specific rules, DI, decorators, money handling
-
Read when relevant:
references/API_SPEC_CONVENTION.md- When designing endpoints, response formats, pagination, bulk opsreferences/SECURITY_CONVENTION.md- When implementing auth, guards, input validationreferences/COMMON_CONVENTION.md- When unsure about naming, git, error codesreferences/TYPESCRIPT_CONVENTION.md- When unsure about TS style, imports, types
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/ - Check existing patterns in the codebase (file naming, DI patterns, existing DTOs)
- If creating a new module, check directory layout in
references/NESTJS_CONVENTION.md
Step 2: Define Domain Model Interface
Create or update the interface in modules/{feature}/interfaces/{feature}.model.interface.ts.
// modules/order/interfaces/order.model.interface.ts
export interface IOrderModel {
id: string;
_no: number;
orderNumber: string;
totalAmount: number;
status: string;
userId: string;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
}
- [MUST] Include only data fields belonging to this model; exclude relations
- [MUST] Entity implements this interface; Mapper uses it as parameter type
- For Entity/TypeORM work, delegate to the
typeorm-devskill
Step 3: Create DTOs
Place DTOs in modules/{feature}/dto/. Follow naming from references/BACKEND_CONVENTION.md.
Critical: Use @sellernote/sellernote-nestjs-api-property exclusively -- never use @ApiProperty, class-validator, or class-transformer directly.
import {
SellernoteApiString,
SellernoteApiNumber,
SellernoteApiDecimal,
} from '@sellernote/sellernote-nestjs-api-property';
export class CreateOrderDto {
@SellernoteApiString({ description: '상품명', maxLength: 100, isRequired: true })
productName: string;
@SellernoteApiNumber({ description: '수량', min: 1, isRequired: true })
quantity: number;
@SellernoteApiDecimal({ description: '총 금액', isRequired: true })
totalAmount: string; // Money fields MUST be string
}
class-validatorandclass-transformerare used internally by@sellernote/sellernote-nestjs-api-property. Always use@SellernoteApi*decorators which wrap these. Seereferences/NESTJS_CONVENTION.mdfor edge cases.
Money fields:
- [MUST] Declare as
stringtype +@SellernoteApiDecimaldecorator - [MUST] Use
big.jsfor arithmetic in Service layer (never nativenumberarithmetic) - [MUST NOT] Use
numbertype or@SellernoteApiNumberfor money
Step 4: Create Controller
Place in modules/{feature}/{feature}.controller.ts. Controller handles HTTP only -- delegate to Service immediately.
- [MUST] Add
@ApiOperation({ summary })and@ApiResponseto every endpoint - [MUST] Use Guards for auth/RBAC, not inline checks
- See
references/BACKEND_ARCHITECTURE_CONVENTION.mdfor layer responsibility rules
Step 5: Create Service
Place in modules/{feature}/{feature}.service.ts. All business logic lives here.
Sellernote-specific patterns:
- [MUST] Use
@Transactional()fromtypeorm-transactionalfor transaction management (notQueryRunner) - [MUST] Use
big.jsfor money calculations:new Big(dto.totalAmount).times(dto.quantity).toFixed(2) - See
references/BACKEND_ARCHITECTURE_CONVENTION.mdfor full Service rules and forbidden patterns
Step 6: Create Repository
Place in modules/{feature}/{feature}.repository.ts. Pure data access only.
- [MUST] Use parameterized queries (never string interpolation)
- See
references/BACKEND_ARCHITECTURE_CONVENTION.mdfor allowed/forbidden Repository patterns
Step 7: Create Mapper
Place in modules/{feature}/mappers/{feature}.mapper.ts.
- [MUST] Use Domain Model Interface (
IXxxModel) as parameter type, not Entity directly
import type { IOrderModel } from '../interfaces/order.model.interface';
export class OrderMapper {
static toResponseDto(model: IOrderModel): OrderResponseDto {
const dto = new OrderResponseDto();
dto.id = model.id;
dto.orderNumber = model.orderNumber;
dto.totalAmount = model.totalAmount.toString(); // number -> string for money
dto.status = model.status;
return dto;
}
}
Step 8: Wire Module
Register all providers in the feature module. Use exports for cross-module access. Avoid @Global() unless truly app-wide.
- See
references/NESTJS_CONVENTION.mdfor module wiring rules
Step 9: Add Guards and Security
Apply JwtAuthGuard, RolesGuard + @Roles() decorator as needed.
- See
references/SECURITY_CONVENTION.mdfor full auth/RBAC/input validation rules - See
references/NESTJS_CONVENTION.mdfor Guard/Interceptor/Pipe usage patterns
Step 10: Verify Swagger
- [MUST] Every DTO field has a
@SellernoteApi*decorator withdescription - [MUST] Every endpoint has
@ApiOperationand@ApiResponse - See
references/NESTJS_CONVENTION.mdfor Swagger documentation rules
File Structure Reference
src/modules/{feature}/
├── interfaces/
│ └── {feature}.model.interface.ts
├── entities/
│ └── {feature}.entity.ts # -> use typeorm-dev skill
├── dto/
│ ├── create-{feature}.dto.ts
│ ├── update-{feature}.dto.ts
│ ├── {feature}-response.dto.ts
│ └── get-{feature}-list-query.dto.ts
├── mappers/
│ └── {feature}.mapper.ts
├── {feature}.module.ts
├── {feature}.controller.ts
├── {feature}.service.ts
├── {feature}.repository.ts
└── {feature}.service.spec.ts
Cross-Skill References
- Entity/TypeORM work: Use the
typeorm-devskill for Entity definitions, migrations, relations, and TypeORM-specific patterns - Entity/Prisma work: Use the
prisma-devskill for Prisma schema models, migrations, relations, and Prisma-specific patterns