일반 리포지토리 기능을 상속받은 커스텀 쿼리가 가능한 리포지토리
일반 리포지토리로도 기본적인 CRUD를 해야하지만 경우에 따라서는 더 복잡한 연산 및 쿼리를 직접 정의해야 하는 경우가 있습니다. 그럴 때는, 일반 리포지토리를 상속받은 커스텀 리포지토리를 작성하여 해결할 수 있어요!
클라이언트가 조회수 순서대로 게시글을 조회하고 싶다고 요청!
Article 엔티티에 view 컬럼을 추가
@Column("int")
view: number; // 새로 추가된 컬럼!
import { Injectable } from "@nestjs/common";
import { DataSource, Repository } from "typeorm";
import { Article } from "./article.entity";
@Injectable()
export class ArticleRepository extends Repository<Article> {
constructor(private dataSource: DataSource) {
super(Article, dataSource.createEntityManager());
}
async getArticlesByViewCount() {
const result = await this.createQueryBuilder()
.select("article")
.from(Article, "article")
.orderBy("article.view", "DESC")
.getMany();
return result;
}
}
providers: [BoardService, ArticleRepository], // ArticleRepository 추가!
getHotArticles라는 새로운 함수 추가. /hot-articles라는 엔드포인트를 호출하면 인기가 많은 순으로 게시물을 가져오는 함수!
// 새롭게 추가한 API
@Get("/hot-articles")
async getHotArticles() {
return await this.boardService.getHotArticles();
// 일반 리포지토리엔 없는 커스텀 리포지터리에만 있는 함수!
async getHotArticles() {
return await this.articleRepository.getArticlesByViewCount();
}
자주 변하지 않는 데이터에 동일한 요청이 지속적으로 들어오는 경우에 대해서는 캐싱 기능을 사용할 수 있다면 서버의 성능이 전반적으로 올라감!
Nest.js에서는 cache-manager와 연계를 하여 캐싱 기능을 사용할 수 있음
npm i cache-manager
npm i -D @types/cache-manager
imports에 추가
CacheModule.register({
ttl: 60000, // 데이터 캐싱 시간(밀리 초 단위, 1000 = 1초)
max: 100, // 최대 캐싱 개수
isGlobal: true,
}),
캐시 매니저를 DI 해보겠습니다. BoardService에서 getArticles 함수에 캐시 매니저를 사용할 것! 서비스 코드를 다음과 같이 바꿔주면 됩니다.
constructor(
// 새로 의존성을 주입한 캐시 매니저!
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
private articleRepository: ArticleRepository
) {}
async getArticles() {
const cachedArticles = await this.cacheManager.get("articles");
if (!_.isNil(cachedArticles)) {
return cachedArticles;
}
const articles = await this.articleRepository.find({
where: { deletedAt: null },
select: ["author", "title", "updatedAt"],
});
await this.cacheManager.set("articles", articles);
return articles;
}
“articles”라는 키를 통해 this.cacheManager.get 함수를 통해서 캐시를 뒤져보고 캐싱이 되었다면 캐싱된 결과(cachedArticles)를 리턴하고 그렇지 않으면 리포지토리로 결과 값을 가져온 뒤 this.cacheManager.set 함수를 통해서 캐싱을 한 후 결과물을 리턴하는 것!
캐싱을 적용해도 크게 문제가 없는 서비스 로직에 한해서는 적극적으로 캐싱을 적용하는 것을 추천합니다! 어차피, max 속성을 통해 캐싱이 되는 데이터 양도 조절을 할 수 있어 메모리가 오버플로우 날 걱정은 딱히하지 않아도 됩니다. 또한, 지금은 인-메모리 기반으로 캐싱을 했지만 Redis와 같은 인-메모리 데이터베이스를 사용하는 것도 가능하니 항상 적극적으로 고려해주세요!
특정 유저(IP 기준)가 짧은 시간내에 여러번 같은 API를 호출하는 것을 방지
npm i @nestjs/throttler
imports: [
ThrottlerModule.forRoot({
ttl: 60,
limit: 10, // ttl 동안 limit 만큼의 요청만 받는다.
}),
],
providers: [
AppService,
AuthMiddleware,
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
ThrottlerModule.forRoot 함수를 통해서 Rate limit 옵션을 글로벌하게 적용해줍니다. limit 이상의 요청을 하게 되면 요청에 응답을 하지 않습니다.
또한, imports 뿐 아니라 providers에도 추가적으로 코드를 작성해주어 글로벌하게 Rate limit을 적용시키도록 합니다.
모든 API도 Rate limit 기능이 적용이 되었기 때문에 DoS 공격으로부터 보호받을 수 있습니다. 하지만, 경우에 따라서는 특정 API는 Rate limit 기능 적용을 하고 싶지 않은 경우도 있겠죠? 그럴때는 @SkipThrottle() 데코레이터를 API위에 명시해주면 됩니다.
@SkipThrottle() // 데코레이터 추가!
@Get("/articles")
async getArticles() {
return await this.boardService.getArticles();
}
Rate limit 정책을 특별하게 적용하고 싶을 때 사용
@Throttle(5, 60) // 이렇게 하면 60초에 5번 호출만 가능!
@Get("/articles/:id")
async getArticleById(@Param("id") articleId: number) {
return await this.boardService.getArticleById(articleId);
}