install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/nestjs-service-pattern" ~/.claude/skills/intense-visions-harness-engineering-nestjs-service-pattern-c99dd8 && rm -rf "$T"
manifest:
agents/skills/codex/nestjs-service-pattern/SKILL.mdsource content
NestJS Service Pattern
Encapsulate business logic in @Injectable services with repository pattern separation
When to Use
- You are implementing business logic that should be reusable across multiple controllers
- You need to interact with a database, external API, or message queue from your application layer
- You want to separate data access concerns (repository) from business rules (service)
- You are writing unit tests and need to mock data access without touching the database
Instructions
- Decorate the class with
. This registers it as a provider that the DI container can instantiate and inject.@Injectable() - Declare the service in the
array of its module. Export it if other modules need to inject it.providers - Inject dependencies through the constructor using TypeScript types — NestJS uses reflection metadata to resolve them.
- Keep one service per domain aggregate (e.g.,
,UsersService
). AvoidOrdersService
catch-alls.AppService - Apply the repository pattern: have the service call a repository (or Prisma/TypeORM directly) rather than embedding query logic inline.
- Throw
subclasses (HttpException
,NotFoundException
, etc.) for domain errors so exception filters can serialize them correctly.ConflictException
@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 } }); } }
- Use
for all async operations. Do not mix callbacks and promises.async/await - Keep methods focused — a method does one thing. Extract helpers for repeated logic.
Details
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.
Source
https://docs.nestjs.com/providers
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.