From harness-claude
Implements NestJS @Injectable services to encapsulate business logic with repository pattern separation using Prisma. Useful for reusable controller logic, database interactions, and unit tests.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Encapsulate business logic in @Injectable services with repository pattern separation
Provides NestJS patterns for scalable Node.js/TypeScript backends: modular architecture, dependency injection, DTO validation, repositories, and events.
Provides expert Nest.js guidance on enterprise Node.js architecture, dependency injection, decorators, middleware, guards, interceptors, pipes, testing strategies, database integration, and authentication. Validates with typecheck, unit, integration, e2e tests.
Organizes NestJS applications using feature modules, controlled exports, dynamic configurations with forRoot/forFeature. Useful for structuring new features, sharing providers without circular dependencies, and building reusable modules.
Share bugs, ideas, or general feedback.
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