project-scaffold
SKILL.md
Project Scaffold
Generate convention-compliant file structures for new NestJS modules or Next.js pages.
Convention Loading
Before starting, Read the relevant reference files from references/ within this skill directory based on scaffold target:
For NestJS Module Scaffold
- Always read first:
references/BACKEND_CONVENTION.md- 3-layer architecture, naming rulesreferences/BACKEND_ARCHITECTURE_CONVENTION.md- Layer responsibilities, dependency directionreferences/NESTJS_CONVENTION.md- Module structure, DI, decorators
- Read when relevant:
references/API_SPEC_CONVENTION.md- When scaffold includes API endpointsreferences/SECURITY_CONVENTION.md- When scaffold needs auth/guardsreferences/TYPEORM_CONVENTION.md- When scaffold includes Entityreferences/DATABASE_CONVENTION.md- When scaffold includes DB modelingreferences/PRISMA_CONVENTION.md- When scaffold uses Prisma instead of TypeORM
For Next.js Page Scaffold
- Always read first:
references/FRONTEND_ARCHITECTURE_CONVENTION.md- Component types, dependency directionreferences/NEXTJS_CONVENTION.md- App Router, Server/Client Components
- Read when relevant:
references/FRONTEND_CONVENTION.md- Frontend common rulesreferences/REACT_CONVENTION.md- React 19 patterns, Hooks, performancereferences/REACT_ROUTER_CONVENTION.md- React Router 7 Framework Mode (for React-only projects)references/STATE_CONVENTION.md- When scaffold includes state managementreferences/STYLING_CONVENTION.md- Tailwind CSS v4, cn(), DS componentsreferences/FORM_CONVENTION.md- When scaffold includes formsreferences/API_CLIENT_CONVENTION.md- When scaffold includes API client setupreferences/API_CLIENT_AXIOS_CONVENTION.md- When using Axios
Common (always read)
references/COMMON_CONVENTION.md- Naming, error codesreferences/TYPESCRIPT_CONVENTION.md- TS style, imports
Workflow
Step 1: Determine Scaffold Type
Ask or infer from context:
| Scaffold Type | When to Use |
|---|---|
| NestJS Module | Backend feature module (API endpoints, business logic, data access) |
| Next.js Page | Frontend page with components, data fetching, state |
| Full-stack | Both backend module and frontend page for one feature |
Identify the feature name (e.g., order, product, shipment) and confirm with the user.
Step 2: NestJS Module Scaffold
Generate the following 11 files under src/modules/{feature}/:
2-1. Domain Model Interface
// src/modules/{feature}/interfaces/{feature}.model.interface.ts
export interface I{Feature}Model {
id: string;
_no: number;
// TODO: Add domain-specific fields here
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
}
2-2. Entity
// src/modules/{feature}/entities/{feature}.entity.ts
import { Column, Entity } from 'typeorm';
import { BaseEntity } from '@/common/entities/base.entity';
import { I{Feature}Model } from '../interfaces/{feature}.model.interface';
@Entity('{feature}')
export class {Feature}Entity extends BaseEntity implements I{Feature}Model {
// TODO: Add @Column() definitions here
// Money fields: use @Column({ type: 'decimal', precision: 20, scale: 4, transformer: DecimalTransformer })
// Enum fields: use @Column({ type: 'varchar', length: 50 })
// Relations: use Relation<T> type wrapper
}
2-3. Create DTO
// src/modules/{feature}/dto/create-{feature}.dto.ts
import {
SellernoteApiString,
} from '@sellernote/sellernote-nestjs-api-property';
export class Create{Feature}Dto {
// TODO: Add @SellernoteApi* decorated fields
// [MUST] Use @SellernoteApiDecimal for money fields (type: string)
// [MUST] Use @SellernoteApiString for text fields
// [MUST] Use @SellernoteApiNumber for numeric fields
}
2-4. Update DTO
// src/modules/{feature}/dto/update-{feature}.dto.ts
import { PartialType } from '@nestjs/swagger';
import { Create{Feature}Dto } from './create-{feature}.dto';
export class Update{Feature}Dto extends PartialType(Create{Feature}Dto) {}
2-5. Response DTO
// src/modules/{feature}/dto/{feature}-response.dto.ts
import {
SellernoteApiString,
SellernoteApiNumber,
} from '@sellernote/sellernote-nestjs-api-property';
export class {Feature}ResponseDto {
@SellernoteApiString({ description: 'UUID', isRequired: true })
id: string;
// TODO: Add response fields with @SellernoteApi* decorators
}
2-6. List Query DTO
// src/modules/{feature}/dto/get-{feature}-list-query.dto.ts
import {
SellernoteApiNumber,
} from '@sellernote/sellernote-nestjs-api-property';
export class Get{Feature}ListQueryDto {
@SellernoteApiNumber({ description: '페이지 번호', min: 1, isRequired: false })
page?: number;
@SellernoteApiNumber({ description: '페이지당 항목 수', min: 1, max: 100, isRequired: false })
limit?: number;
// TODO: Add filter fields
}
2-7. Mapper
// src/modules/{feature}/mappers/{feature}.mapper.ts
import type { I{Feature}Model } from '../interfaces/{feature}.model.interface';
import { {Feature}ResponseDto } from '../dto/{feature}-response.dto';
export class {Feature}Mapper {
static toResponseDto(model: I{Feature}Model): {Feature}ResponseDto {
const dto = new {Feature}ResponseDto();
dto.id = model.id;
// TODO: Map remaining fields
return dto;
}
}
2-8. Repository
// src/modules/{feature}/{feature}.repository.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { {Feature}Entity } from './entities/{feature}.entity';
@Injectable()
export class {Feature}Repository {
constructor(
@InjectRepository({Feature}Entity)
private readonly repository: Repository<{Feature}Entity>,
) {}
// TODO: Add data access methods
// [MUST] Use parameterized queries (never string interpolation)
}
2-9. Service
// src/modules/{feature}/{feature}.service.ts
import { Injectable } from '@nestjs/common';
import { {Feature}Repository } from './{feature}.repository';
@Injectable()
export class {Feature}Service {
constructor(private readonly {feature}Repository: {Feature}Repository) {}
// TODO: Add business logic methods
// [MUST] Use @Transactional() for multi-step DB operations
// [MUST] Use big.js for money calculations
}
2-10. Controller
// src/modules/{feature}/{feature}.controller.ts
import { Controller, Get, Post, Put, Delete, Param, Body, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { {Feature}Service } from './{feature}.service';
import { Create{Feature}Dto } from './dto/create-{feature}.dto';
import { Update{Feature}Dto } from './dto/update-{feature}.dto';
import { Get{Feature}ListQueryDto } from './dto/get-{feature}-list-query.dto';
@ApiTags('{feature}')
@Controller('{feature}')
export class {Feature}Controller {
constructor(private readonly {feature}Service: {Feature}Service) {}
// TODO: Add endpoint methods
// [MUST] Add @ApiOperation and @ApiResponse to every endpoint
// [MUST] Delegate all logic to Service immediately
}
2-11. Module
// src/modules/{feature}/{feature}.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { {Feature}Entity } from './entities/{feature}.entity';
import { {Feature}Controller } from './{feature}.controller';
import { {Feature}Service } from './{feature}.service';
import { {Feature}Repository } from './{feature}.repository';
@Module({
imports: [TypeOrmModule.forFeature([{Feature}Entity])],
controllers: [{Feature}Controller],
providers: [{Feature}Service, {Feature}Repository],
exports: [{Feature}Service],
})
export class {Feature}Module {}
Step 3: Next.js Page Scaffold
Generate the following files:
3-1. Page (Server Component)
// app/(group)/{feature}/page.tsx
import { {Feature}List } from '@/components/feature/{Feature}/{Feature}List';
export default function {Feature}Page() {
return <{Feature}List />;
}
3-2. Loading
// app/(group)/{feature}/loading.tsx
import { Skeleton, Stack } from '@mui/material';
export default function {Feature}Loading() {
return (
<Stack spacing={2}>
<Skeleton variant="rectangular" height={40} />
<Skeleton variant="rectangular" height={400} />
</Stack>
);
}
3-3. Error
// app/(group)/{feature}/error.tsx
'use client';
export default function {Feature}Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>오류가 발생했습니다</h2>
<button onClick={reset}>다시 시도</button>
</div>
);
}
3-4. Detail Page
// app/(group)/{feature}/[id]/page.tsx
import { {Feature}Detail } from '@/components/feature/{Feature}/{Feature}Detail';
export default function {Feature}DetailPage({
params,
}: {
params: { id: string };
}) {
return <{Feature}Detail id={params.id} />;
}
3-5. Feature Component (List)
// components/feature/{Feature}/{Feature}List.tsx
'use client';
import { use{Feature}ListQuery } from '@/queries/use{Feature}ListQuery';
export function {Feature}List() {
const { data, isLoading } = use{Feature}ListQuery();
// TODO: Implement list UI using data
return <div>TODO: {Feature} List</div>;
}
3-6. Feature Component (Detail)
// components/feature/{Feature}/{Feature}Detail.tsx
'use client';
import { use{Feature}Query } from '@/queries/use{Feature}Query';
interface {Feature}DetailProps {
id: string;
}
export function {Feature}Detail({ id }: {Feature}DetailProps) {
const { data, isLoading } = use{Feature}Query(id);
// TODO: Implement detail UI using data
return <div>TODO: {Feature} Detail</div>;
}
3-7. Query Hooks
// queries/use{Feature}ListQuery.ts
import { useQuery } from '@tanstack/react-query';
import { {feature}Keys } from './queryKeys/{feature}Keys';
export function use{Feature}ListQuery(params?: Record<string, unknown>) {
return useQuery({
queryKey: {feature}Keys.list(params),
queryFn: async () => {
// TODO: Implement API call
},
});
}
// queries/use{Feature}Query.ts
import { useQuery } from '@tanstack/react-query';
import { {feature}Keys } from './queryKeys/{feature}Keys';
export function use{Feature}Query(id: string) {
return useQuery({
queryKey: {feature}Keys.detail(id),
queryFn: async () => {
// TODO: Implement API call
},
enabled: !!id,
});
}
// queries/queryKeys/{feature}Keys.ts
export const {feature}Keys = {
all: ['{feature}'] as const,
lists: () => [...{feature}Keys.all, 'list'] as const,
list: (params?: Record<string, unknown>) => [...{feature}Keys.lists(), params] as const,
details: () => [...{feature}Keys.all, 'detail'] as const,
detail: (id: string) => [...{feature}Keys.details(), id] as const,
};
3-8. Types
// types/{Feature}.types.ts
export interface {Feature} {
id: string;
// TODO: Add type fields matching API response
createdAt: string;
updatedAt: string;
}
export interface {Feature}ListResponse {
list: {Feature}[];
total: number;
}
Step 4: Post-Scaffold Summary
After generating files, present:
- Created files list with paths
- TODO markers that need user attention (fields, business logic)
- Next steps:
- For NestJS: "Register
{Feature}ModuleinAppModuleimports" - For Next.js: "Add route to navigation/sidebar if needed"
- For NestJS: "Register
- Recommended skills for detailed implementation:
nestjs-api-devfor API endpoint detailstypeorm-devfor Entity column definitions and migrationsnextjs-data-providerfor query hooks and server actionsnextjs-ui-devfor component UI implementation
Key Rules Summary
| Rule | Detail |
|---|---|
| MUST | Use @sellernote/sellernote-nestjs-api-property for all DTO decorators |
| MUST | Entity extends custom BaseEntity (not TypeORM's) |
| MUST | Domain Model Interface in interfaces/ directory |
| MUST | Mapper uses I{Feature}Model type, not Entity directly |
| MUST | Money fields as string type with @SellernoteApiDecimal |
| MUST | page.tsx is Server Component -- no business logic |
| MUST | Feature components in components/feature/ with 'use client' |
| MUST | Query key factory pattern ({feature}Keys) |
| MUST | @/ absolute import paths in Next.js |
| MUST | TODO markers at every point requiring user customization |
Cross-Skill References
- NestJS API implementation details: Use the
nestjs-api-devskill - Entity/TypeORM work: Use the
typeorm-devskill - Next.js data layer: Use the
nextjs-data-providerskill - Next.js UI components: Use the
nextjs-ui-devskill - Full feature orchestration: Use the
nextjs-dev-orchestrationskill - Entity/Prisma work: Use the
prisma-devskill
Weekly Installs
1
Repository
sellernote/sell…e-pluginFirst Seen
11 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1