算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
nestjs-architecture【设计与创意】Use this skill when structuring NestJS applications — module-based architecture, dependency injection, provider scopes, dynamic modules, and monorepo structure. This skill enforces: one module per domain, provider encapsulation, module boundary enforcement, global vs scoped providers. Requires @nestjs/core. Do NOT use for: Express-only projects, Reac…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Structure NestJS applications with module-based architecture — domain modules, shared modules, dynamic modules, monorepo projects, and provider scoping.
User request includes: NestJS module structure, NestJS architecture, NestJS project layout, NestJS DI, NestJS modules, NestJS monorepo, NestJS providers.
Module structure, provider registration, module exports, monorepo layout, DI setup.
Produce artifact directly. No preamble, no postamble, no explanations.
4096 tokens
| Module Type | Contains | Export Strategy | Example |
|---|---|---|---|
| Feature | Controllers, Services, Repositories | Only what others need | UsersModule, OrdersModule |
| Shared | Reusable utilities, guards, filters | Everything | CommonModule |
| Core | Singletons (DB, config, logger) | Global | DatabaseModule, ConfigModule |
Decision: Domain logic → Feature Module. Cross-cutting concern → Shared Module. Infrastructure singleton → Global Core Module.
| Scope | Lifetime | Use Case |
|---|---|---|
| DEFAULT (singleton) | One per app | Services, repositories, DB connections |
| TRANSIENT | New per injection | Stateful validators, per-request cache |
| REQUEST | New per HTTP request | Request-scoped data (tenant, user) |
Decision: Stateless service → DEFAULT. Stateful per-instance → TRANSIENT. Per-request data → REQUEST.
src/
main.ts # Bootstrap
app.module.ts # Root module
app.controller.ts # Health check
common/
decorators/
current-user.decorator.ts
roles.decorator.ts
guards/
auth.guard.ts
roles.guard.ts
filters/
http-exception.filter.ts
interceptors/
logging.interceptor.ts
transform.interceptor.ts
pipes/
validation.pipe.ts
interfaces/
user-request.interface.ts
modules/
users/
user.module.ts
user.controller.ts
user.service.ts
user.repository.ts
user.entity.ts
dto/
create-user.dto.ts
update-user.dto.ts
orders/
order.module.ts
order.controller.ts
order.service.ts
order.entity.ts
dto/
config/
database.config.ts
app.config.ts
// modules/users/user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { UserRepository } from './user.repository';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService, UserRepository],
exports: [UserService],
})
export class UsersModule {}
// modules/users/user.controller.ts
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async findAll(@Query() pagination: PaginationDto) {
return this.userService.findAll(pagination);
}
@Get(':id')
async findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.userService.findById(id);
}
@Post()
@UsePipes(new ValidationPipe({ whitelist: true }))
async create(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
@Patch(':id')
async update(@Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateUserDto) {
return this.userService.update(id, dto);
}
@Delete(':id')
@HttpCode(204)
async remove(@Param('id', ParseUUIDPipe) id: string) {
return this.userService.delete(id);
}
}
// modules/users/user.service.ts
@Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly eventEmitter: EventEmitter2,
) {}
async create(dto: CreateUserDto): Promise<User> {
const existing = await this.userRepository.findByEmail(dto.email);
if (existing) {
throw new ConflictException('Email already exists');
}
const user = await this.userRepository.save(dto.toEntity());
this.eventEmitter.emit('user.created', user);
return user;
}
}
// common/decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
// common/guards/auth.guard.ts
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractToken(request);
if (!token) throw new UnauthorizedException();
try {
const payload = this.jwtService.verify(token);
request.user = payload;
return true;
} catch {
throw new UnauthorizedException();
}
}
private extractToken(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
// common/interceptors/transform.interceptor.ts
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
return next.handle().pipe(
map(data => ({
success: true,
data,
timestamp: new Date().toISOString(),
})),
);
}
}
// common/interceptors/logging.interceptor.ts
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger('HTTP');
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const { method, url } = request;
const now = Date.now();
return next.handle().pipe(
tap(() => this.logger.log(`${method} ${url} ${Date.now() - now}ms`)),
);
}
}
// config/database.config.ts
export interface DatabaseModuleOptions {
type: 'postgres' | 'mysql' | 'sqlite';
host: string;
port: number;
username: string;
password: string;
database: string;
}
@Module({})
export class DatabaseModule {
static forRoot(options: DatabaseModuleOptions): DynamicModule {
return {
module: DatabaseModule,
global: true,
imports: [
TypeOrmModule.forRoot({
type: options.type,
host: options.host,
port: options.port,
username: options.username,
password: options.password,
database: options.database,
autoLoadEntities: true,
synchronize: false,
logging: process.env.NODE_ENV === 'development',
}),
],
providers: [
{
provide: 'DATABASE_OPTIONS',
useValue: options,
},
],
exports: ['DATABASE_OPTIONS'],
};
}
static forFeature(models: EntityClassOrSchema[]): DynamicModule {
return {
module: DatabaseModule,
imports: [TypeOrmModule.forFeature(models)],
exports: [TypeOrmModule],
};
}
}
// Usage in app.module.ts
@Module({
imports: [
DatabaseModule.forRoot({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
}),
UsersModule,
OrdersModule,
CommonModule,
],
controllers: [AppController],
providers: [
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
{ provide: APP_FILTER, useClass: HttpExceptionFilter },
],
})
export class AppModule {}
apps/
api/
src/
main.ts
app.module.ts
admin/
src/
main.ts
admin.module.ts
worker/
src/
main.ts
worker.module.ts
libs/
common/
src/
decorators/
guards/
interceptors/
filters/
common.module.ts
database/
src/
database.module.ts
entities/
repositories/
shared/
types/
interfaces/
nest-cli.json
// nest-cli.json (monorepo)
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"monorepo": true,
"root": "apps/api",
"projects": {
"api": { "type": "application", "root": "apps/api", "entryFile": "main" },
"admin": { "type": "application", "root": "apps/admin", "entryFile": "main" },
"worker": { "type": "application", "root": "apps/worker", "entryFile": "main" },
"common": { "type": "library", "root": "libs/common" },
"database": { "type": "library", "root": "libs/database" }
}
}
// config/app.config.ts
export default registerAs('app', () => ({
port: parseInt(process.env.PORT, 10) || 3000,
corsOrigin: process.env.CORS_ORIGIN || '*',
jwtSecret: process.env.JWT_SECRET,
jwtExpiresIn: process.env.JWT_EXPIRES_IN || '1d',
database: {
url: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production',
},
}));
// Usage in service
@Injectable()
export class AppService {
constructor(
@InjectConfig() private readonly appConfig: ConfigType<typeof appConfig>,
) {}
getPort(): number {
return this.appConfig.port;
}
}
// Create command
export class CreateUserCommand {
constructor(public readonly dto: CreateUserDto) {}
}
// Command handler
@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
constructor(private readonly userRepository: UserRepository) {}
async execute(command: CreateUserCommand): Promise<User> {
const { dto } = command;
return this.userRepository.save(dto.toEntity());
}
}
// Controller dispatches
@Post()
async create(@Body() dto: CreateUserDto) {
return this.commandBus.execute(new CreateUserCommand(dto));
}
// Event
export class UserCreatedEvent {
constructor(public readonly user: User) {}
}
// Event handler
@EventsHandler(UserCreatedEvent)
export class UserCreatedHandler implements IEventHandler<UserCreatedEvent> {
constructor(
private readonly emailService: EmailService,
private readonly analyticsService: AnalyticsService,
) {}
async handle(event: UserCreatedEvent) {
await Promise.all([
this.emailService.sendWelcome(event.user),
this.analyticsService.track('user.created', event.user.id),
]);
}
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
app.enableCors({ origin: process.env.CORS_ORIGIN });
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));
app.useGlobalInterceptors(new TransformInterceptor());
const port = process.env.PORT || 3000;
await app.listen(port);
console.log(`Application running on port ${port}`);
}
// Register OpenTelemetry SDK
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
const otelSDK = new NodeSDK({
instrumentations: [getNodeAutoInstrumentations()],
});
otelSDK.start();
| Anti-Pattern | Why | Fix |
|---|---|---|
| Circular module imports | DI fails at runtime | Use shared module or forwardRef |
| All providers in root module | No encapsulation, poor reusability | Per-feature modules with exports |
| Global guards registered inline | Can't test without full module | Use APP_GUARD token in providers |
| Services with multiple repo injections | SRP violation | Split into smaller services |
| Directly importing TypeORM module in feature | Tight coupling to ORM | Use repository pattern via DI |
ValidationPipe with whitelist: true to strip unknown propertiesParseUUIDPipe on all ID params to prevent injectionapp.use(helmet()) at bootstrap@nestjs/throttler modulecsurf middleware for cookie-based auth@Exclude() / @ClassSerializerInterceptor to hide sensitive fieldsdescribe('UserService', () => {
let service: UserService;
let repository: MockType<UserRepository>;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UserService,
{ provide: UserRepository, useFactory: repositoryMockFactory },
{ provide: EventEmitter2, useValue: { emit: jest.fn() } },
],
}).compile();
service = module.get(UserService);
repository = module.get(UserRepository);
});
it('should throw on duplicate email', async () => {
repository.findByEmail.mockResolvedValue({ email: 'test@test.com' });
await expect(service.create({ email: 'test@test.com', name: 'Test' }))
.rejects.toThrow(ConflictException);
});
});
Use @nestjs/testing for unit tests. Use supertest with createNestApplication for e2e tests. Use TestContainers for database integration tests.
APP_* tokens (APP_GUARD, APP_INTERCEPTOR, APP_FILTER, APP_PIPE) in root module only.@Inject() on properties.@Injectable(), controllers are @Controller(), both in module providers/controllers.Hand off to backend/nestjs/patterns/SKILL.md for NestJS-specific patterns or backend/universal/api-response/SKILL.md for API response formatting.