NestJS

Lesson 08

Repository and service boundaries

Keep NestJS services focused on application rules and put database details behind repository providers.

Good Code

reviews.service.ts
import { Injectable, NotFoundException } from "@nestjs/common";

@Injectable()
export class ReviewsService {
  constructor(private readonly reviews: ReviewsRepository) {}

  // The service owns the use case and the repository owns the query.
  async approve(reviewId: string, reviewerId: string) {
    const review = await this.reviews.findPendingById(reviewId);
    if (!review) throw new NotFoundException("Review not found");

    return this.reviews.markApproved(reviewId, reviewerId);
  }
}

Bad Code

reviews.service.ts
import { Injectable } from "@nestjs/common";

@Injectable()
export class ReviewsService {
  async approve(reviewId: string, reviewerId: string) {
    // SQL details and application rules are tangled in one method.
    const review = await db.query("select * from reviews where id = $1", [reviewId]);
    await db.query("update reviews set status = 'approved' where id = $1", [
      reviewId,
    ]);
    return review.rows[0];
  }
}

Review Notes

What to review

Good Code

The good version lets the service read like a use case: find a pending review, reject missing data, then approve it.

Bad Code

The bad version mixes SQL details with application decisions. Query shape, status transitions, and error semantics are harder to test separately.

Takeaways

  • Services should express use cases while repositories express database queries and persistence details.