SOC 직업 분류 기준
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/CodySwannGT/lisa --skill nestjs-rules명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
This skill should be used for any non-trivial request — features, bugs, stories, epics, spikes, or multi-step tasks. It accepts a ticket URL (Jira, Linear, GitHub), a file path containing a spec, or a plain-text prompt. It assembles an agent team, breaks the work into structured tasks, and manages the full lifecycle from research through implementation, code review, deploy, and empirical verification.
any non-trivial request —…
This skill should be used for any non-trivial request — features, bugs, stories, epics, spikes, or multi-step tasks. It accepts a ticket URL (Jira, Linear, GitHub), a file path containing a spec, or a plain-text prompt. It assembles an agent team, breaks the work into structured tasks, and manages the full lifecycle from research through implementation, code review, deploy, and empirical verification.
| name | nestjs-rules |
| description | Procedural rules and patterns… |
This skill provides procedural rules for working with NestJS in this project. It covers component generation, testing patterns, module structure, and deployment configuration. For GraphQL-specific patterns (resolvers, types, auth decorators), use the nestjs-graphql skill.
Always use NestJS CLI to create components rather than manually creating files.
bunx nest g module <name> --no-spec
bunx nest g service <name> --no-spec
bunx nest g resolver <name> --no-spec
bunx nest g controller <name> --no-spec
--no-specThe --no-spec flag is used because this project follows TDD (Test-Driven Development). Tests are written first with a custom test structure before implementation, not auto-generated by the CLI.
In this project, entity files (src/database/entities/*.ts) are the single source of truth for the database schema. Migrations are a derived artifact — TypeORM diffs the entity metadata against the current database and emits the migration for you. The workflow is always:
bun run migration:generate --name=<DescriptiveName> to produce the migration from the diff.If a schema change cannot be expressed via the entity model, the entity model is wrong — fix the entity, do not hand-write the migration.
Never create or modify a TypeORM migration file directly. Use migration:generate from package.json:
bun run migration:generate --name=<DescriptiveName>
Some changes genuinely cannot be derived from entity diffs:
These are legitimate cases for a hand-written migration, but they are out-of-band — they bypass the entity-as-source-of-truth contract. When you encounter one:
Do not silently hand-write a migration for a backfill or seed-data change. The user must know that the entity-as-source-of-truth contract is being intentionally bypassed for this case.
The lisa-nestjs plugin ships a PreToolUse hook (block-migration-edits.sh) that blocks Write/Edit on any path matching **/migrations/*.ts or **/migrations/*.js. The block surfaces this rule's guidance to remind you to either (a) edit the entity instead, or (b) ask the user before proceeding with an out-of-band migration.
TypeORM compares your entity definitions against the current database schema to generate migrations. Manual creation can:
Create test files before implementation:
// src/feature/feature.service.test.ts
import { Test, TestingModule } from "@nestjs/testing";
import { FeatureService } from "./feature.service";
describe("FeatureService", () => {
const service: FeatureService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [FeatureService],
}).compile();
service = module.get<FeatureService>(FeatureService);
});
describe("methodName", () => {
it("should do expected behavior", () => {
expect(service.methodName()).toBe("expected");
});
});
});
*.test.ts*.integration.test.ts# Unit tests only
bun run test:unit
# Integration tests only
bun run test:integration
# All tests
bun run test
src/
├── <feature>/
│ ├── <feature>.module.ts # Module definition
│ ├── <feature>.service.ts # Business logic
│ ├── <feature>.service.test.ts # Service unit tests
│ ├── <feature>.resolver.ts # GraphQL resolver (if applicable)
│ ├── <feature>.resolver.test.ts # Resolver unit tests
│ ├── <feature>.controller.ts # REST controller (if applicable)
│ ├── <feature>.controller.test.ts
│ ├── dto/ # Data transfer objects
│ │ ├── create-<feature>.input.ts
│ │ └── update-<feature>.input.ts
│ └── entities/ # Entity definitions
│ └── <feature>.entity.ts
Register feature modules in app.module.ts:
import { Module } from "@nestjs/common";
import { FeatureModule } from "./feature/feature.module";
@Module({
imports: [
// ... other imports
FeatureModule,
],
})
export class AppModule {}
// src/main.ts
import { NestFactory } from "@nestjs/core";
import { configure as serverlessExpress } from "@vendia/serverless-express";
import { AppModule } from "./app.module";
type ServerlessHandler = ReturnType<typeof serverlessExpress>;
/**
* Creates a lazy-initialized server getter using closure pattern
* @description Encapsulates mutable cache state for Lambda warm starts
* @returns Async function that returns the cached or newly created server
*/
const createServerGetter = (): (() => Promise<ServerlessHandler>) => {
// eslint-disable-next-line functional/no-let -- Required for Lambda warm start caching
let cachedServer: ServerlessHandler | null = null;
return async (): Promise<ServerlessHandler> => {
if (cachedServer) {
return cachedServer;
}
const nestApp = await NestFactory.create(AppModule, {
cors: {
origin: "*",
methods: ,
: ,
: ,
},
});
nestApp.();
app = nestApp.().();
cachedServer = ({ app });
cachedServer;
};
};
getServer = ();
handler = (
: ,
:
): <> => {
server = ();
(event, context);
};
@vendia/serverless-express for Express adapter compatibility// nest-cli.json
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}
Required settings for NestJS decorators:
// tsconfig.json (additions)
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false
}
}
# serverless.yml
service: project-name
frameworkVersion: "^4.0.0"
custom:
esbuild:
bundle: true
minify: false
sourcemap: true
keepNames: true
platform: node
target: node20
external:
- "fsevents"
- "@nestjs/websockets"
- "@nestjs/microservices"
- "@apollo/gateway"
- "@apollo/subgraph"
- "@as-integrations/fastify"
- "class-transformer/storage"
plugins:
- serverless-esbuild
- serverless-offline
provider:
name: aws
runtime: nodejs22.x
region: us-east-1
httpApi:
cors: true
functions:
main:
handler: src/main.handler
timeout: 29
Every exported function, class, and type must have JSDoc documentation:
/**
* Service for managing user accounts
* @description Provides CRUD operations for user entities
* @remarks
* - All methods are idempotent
* - Throws NotFoundException for missing resources
*/
@Injectable()
export class UserService {
/**
* Retrieves a user by their unique identifier
* @param id - The unique identifier of the user
* @returns The user if found, null otherwise
*/
async findById(id: string): Promise<User | null> {
return this.repository.findOne({ where: { id } });
}
}
Every file should have a preamble comment:
/**
* @file user.service.ts
* @description Service providing user account management
* @module users
*/
Services that support DataLoader must implement batch methods:
/**
* Batch loads entities by IDs (for DataLoader)
* @param ids - Array of entity IDs to load
* @returns Promise resolving to array of entities in same order as input
* @remarks Used by DataLoader for batching - maintains input order
*/
async findByIds(ids: readonly string[]): Promise<Entity[]> {
const entities = await this.repository.findBy({ id: In([...ids]) });
const entityMap = new Map(entities.map(e => [e.id, e]));
return ids.map(id => entityMap.get(id) ?? null);
}
Batch functions must return results in the same order as input keys. Always map input IDs to results to maintain order.
Use const instead of let or var:
// Good
const users = await this.userService.findAll();
const filtered = users.filter(u => u.active);
// Bad
let users = await this.userService.findAll();
users = users.filter(u => u.active);
Use reduce instead of push or pop:
// Good
const userMap = users.reduce(
(acc, user) => ({ ...acc, [user.id]: user }),
{} as Record<string, User>
);
// Bad
const userMap: Record<string, User> = {};
users.forEach(user => {
userMap[user.id] = user;
});
Use GraphQL errors with codes:
import { GraphQLError } from "graphql";
throw new GraphQLError("User not found", {
extensions: { code: "NOT_FOUND", id },
});
Always use NestJS ConfigService instead of accessing process.env directly. This provides:
For services, resolvers, and controllers running within NestJS:
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Configuration } from "../config/configuration";
@Injectable()
export class MyService {
constructor(
private readonly configService: ConfigService<Configuration, true>
) {}
someMethod(): void {
// Type-safe configuration access with autocomplete
const host = this.configService.get("database.host", { infer: true });
const isOffline = this.configService.get("app.isOffline", { infer: true });
}
}
For dynamic module configuration (e.g., TypeOrmModule.forRootAsync):
import { Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Configuration } from "../config/configuration";
@Module({
imports: [
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService<Configuration, true>) =>
createTypeOrmOptionsFromConfigService(configService),
}),
],
})
export class DatabaseModule {}
For code running outside NestJS context (Lambda authorizers, WebSocket handlers):
import { getStandaloneConfig } from "../../config/configuration";
// Use getStandaloneConfig() for type-safe access outside NestJS
const config = getStandaloneConfig();
const host = config.valkey.host;
const port = config.valkey.port;
All configuration is defined in src/config/configuration.ts:
export interface Configuration {
readonly app: {
readonly nodeEnv: string;
readonly isOffline: boolean;
};
readonly database: {
readonly host: string;
readonly port: number;
readonly username: string;
readonly password: string;
readonly name: string;
// ... other database config
};
readonly valkey: {
readonly host: string;
readonly port: number;
readonly maxRetriesPerRequest: number;
};
// ... other configuration namespaces
}
Configuration interfaceconfiguration() factory functionconfigService.get("namespace.property", { infer: true })Create mock ConfigService in tests:
const createMockConfigService = (): ConfigService<Configuration, true> => {
const config = {
valkey: { host: "localhost", port: 6379, maxRetriesPerRequest: 3 },
};
return {
get: jest.fn((key: string) => {
const keys = key.split(".");
return keys.reduce((obj, k) => obj?.[k], config);
}),
} as unknown as ConfigService<Configuration, true>;
};
// In test setup
const module = await Test.createTestingModule({
providers: [
MyService,
{ provide: ConfigService, useValue: createMockConfigService() },
],
}).compile();
After creating or modifying NestJS components:
bun run test:unitbun run test:integrationbun run lintbun run buildbun run start:local