From harness-claude
Encapsulates business logic in @Injectable services with repository pattern separation for NestJS applications. Useful for reusable logic, database interaction, and testable code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:nestjs-service-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Encapsulate business logic in @Injectable services with repository pattern separation
Encapsulate business logic in @Injectable services with repository pattern separation
@Injectable(). This registers it as a provider that the DI container can instantiate and inject.providers array of its module. Export it if other modules need to inject it.UsersService, OrdersService). Avoid AppService catch-alls.HttpException subclasses (NotFoundException, ConflictException, etc.) for domain errors so exception filters can serialize them correctly.@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
async findOne(id: string): Promise<User> {
const user = await this.prisma.user.findUnique({ where: { id } });
if (!user) throw new NotFoundException(`User ${id} not found`);
return user;
}
async create(dto: CreateUserDto): Promise<User> {
const existing = await this.prisma.user.findUnique({ where: { email: dto.email } });
if (existing) throw new ConflictException('Email already registered');
return this.prisma.user.create({ data: dto });
}
async update(id: string, dto: UpdateUserDto): Promise<User> {
await this.findOne(id); // reuse findOne for existence check
return this.prisma.user.update({ where: { id }, data: dto });
}
async remove(id: string): Promise<void> {
await this.findOne(id);
await this.prisma.user.delete({ where: { id } });
}
}
async/await for all async operations. Do not mix callbacks and promises.Services are the workhorses of NestJS applications. They are singleton-scoped by default (one instance shared across the entire app lifetime), which means you should not store per-request state in service instance variables.
Scope options: @Injectable({ scope: Scope.REQUEST }) creates a new instance per request — useful for request-scoped data (e.g., tenant ID) but has a performance cost since the entire dependency chain must be request-scoped. Use the default DEFAULT (singleton) scope unless you have a concrete reason not to.
Repository pattern with Prisma: Rather than calling prisma.user everywhere in a service, some teams create a UsersRepository class that wraps Prisma calls and exposes domain-level methods (findByEmail, findActiveUsers). This makes the service easier to test and the data access logic easier to swap.
Error boundaries: Only throw HTTP exceptions in the service if it is HTTP-facing. For domain services shared between HTTP and microservice transports, throw domain-specific exceptions and convert them to RpcException or HttpException at the transport layer.
Circular dependency between services: When ServiceA depends on ServiceB and vice versa, inject with forwardRef(): @Inject(forwardRef(() => ServiceB)) private serviceB: ServiceB. Prefer refactoring to a shared third service instead.
https://docs.nestjs.com/providers
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeOrganizes NestJS applications with cohesive feature modules, controlled exports, and composable dynamic configurations. Useful when structuring providers, avoiding circular dependencies, or building reusable configurable modules.
Applies opinionated NestJS conventions for backends: modules, dependency injection, controllers/services, DTOs with class-validator, guards/interceptors/pipes, JWT auth, TypeORM/Prisma. Use for REST/GraphQL APIs.
Enforces NestJS best practices for modular architecture, dependency injection scoping, exception filters, class-validator DTO validation, and Drizzle ORM integration. Use when designing modules, providers, filters, DTOs, or ORM in NestJS apps.