본문 바로가기
개발 기초 다지기

카테고리별로 공연 목록을 조회 (쿼리 vs URL 경로)

by 너의고래 2024. 7. 4.

Typescript와 Nest.js를 이용한 첫 과제를 진행중이다. entity와 dto는 스키마와 validator느낌이라 괜찮은편이지만 module쪽은 모든 코드에 주석을 달면서했음에도 불구하고 아직도 이해가 쉽지 않다. 그래도 강의를 참고하고 이것저것 찾아가며 구현을 해나가고 있다. 그 중 오늘 구현한 공연 목록 조회. 그 중 쿼리를 이용한 전체/카테고리 별로 선택해서 보는 것을 정리해보려 한다.

 

'전체, 카테고리 별로 나누어서 조회' 어떻게 해야할까?

 

위 이미지처럼 콘서트를 클릭하면, 콘서트 카테고리에 속한 공연 목록들이 반환돼야하는 것이다.

 

 

인터파크 티켓의 경우에는 URL경로를 통해 목록을 조회했다.

나는 보통 이런 정렬 기능을 사용할 때, 쿼리파라미터를 사용하는 것이 적절하다고 들어왔기에 둘의 장단점을 찾아보았다.

 

 

쿼리 파라미터 vs URL 경로

1. 쿼리 파라미터 사용

예: localhost:3000/performances?category=CLASSIC

장점:

  1. 유연성: 여러 개의 필터를 쉽게 추가할 수 있으며, 각 필터가 독립적으로 동작한다.
    • 예: localhost:3000/performances?category=CLASSIC&date=2024-08-01
  2. URL 가독성: URL이 짧고 가독성이 좋다. 필요한 필터를 URL에 직관적으로 추가할 수 있다.
  3. 캐싱: 쿼리 파라미터가 있는 URL은 쉽게 캐싱할 수 있어 성능 향상에 도움이 된다.
  4. 리소스 절약: 다양한 필터링 옵션을 지원하기 위해 추가적인 엔드포인트를 생성할 필요가 없다.

단점:

  1. 제한된 RESTful 표현: 쿼리 파라미터는 RESTful 원칙에 완전히 부합하지 않을 수 있다. RESTful URL 구조에서 리소스와 행위는 경로에 포함하는 것이 권장된다.
  2. 복잡성 증가: 여러 개의 필터를 조합하여 사용할 경우 URL이 복잡해질 수 있다.
  3. SEO 영향: 검색 엔진 최적화(SEO)에 영향을 미칠 수 있다. 검색 엔진이 쿼리 파라미터를 무시하거나 덜 중요하게 여길 수 있다.

2. URL 경로 사용

예: https://tickets.interpark.com/contents/genre/concert

장점:

  1. RESTful 원칙 준수: URL 경로에 리소스와 행위를 포함하여 보다 직관적이고 RESTful한 구조를 갖는다.
  2. 가독성: 특정 리소스를 가리키는 URL이 보다 직관적이다.
    • 예: localhost:3000/performances/classic
  3. SEO 친화적: 경로 기반 URL은 검색 엔진에서 더 높은 가치를 가질 수 있다.
  4. 단순성: 단순한 필터링 조건을 명확하게 표현할 수 있다.

단점:

  1. 유연성 부족: 여러 개의 필터를 추가하는 데 적합하지 않다. 필터 조건이 많아지면 경로 기반 접근 방식이 비효율적일 수 있다.
    • 예: localhost:3000/performances/classic/2024-08-01 (필터 추가가 어려움)
  2. 엔드포인트 증가: 다양한 필터 조건을 지원하기 위해 많은 엔드포인트를 생성해야 할 수 있습니다.
  3. 변경 어려움: 필터 조건이 자주 변경되거나 동적으로 적용될 필요가 있는 경우 경로 기반 접근 방식은 덜 유연하다.

결론

  • 쿼리 파라미터는 유연하고 확장 가능한 필터링을 제공하며, 다양한 조건을 쉽게 추가할 수 있다.
  • URL 경로는 RESTful한 디자인을 유지하고 단순한 필터링 조건을 명확하게 표현하는 데 유리하다.

선택은 주로 API의 사용 패턴, 복잡성, RESTful 원칙 준수 여부, 그리고 SEO 요구사항에 따라 달라질 수 있습니다. 경우에 따라 두 방식을 혼합하여 사용할 수도 있습니다.

 

 

나의 선택 (쿼리파라미터)

RESTful 원칙 준수에서 URL 경로 사용이 고민되었지만, 현재 나는 검색 엔진 최적화가 필요없고 여러 개의 필터를 쉽게 추가할 수 있다는 장점으로 쿼리파라미터를 선택했다. (무엇보다 내가 실제로 쿼리파라미터를 한 번도 사용해본적이 없어서 연습겸 선택하기도 했다.)

 

 

실제 구현

- performance.controller.ts

  //공연 목록 조회
  @Get()
  async findAll(@Query('category') category?: string) {
    if(category) {
        return {
            status: 200,
            message: `${category} 카테고리의 공연 목록 조회에 성공했습니다.`,
            data: await this.performanceService.findAll(category)
        }
    }else {
        return{
            status: 200,
            message: '공연 목록 조회에 성공했습니다.',
            data: await this.performanceService.findAll()
        }
    }
}

 

  • controller 단에서는 findAll에 쿼리에서 온 category를 넣어주면된다.
  • 그리고 반환시 메세지의 경우의 수를 2가지로 나누어서 메세지에 카테고리명이 들어가도록 수정해주었다. 

 

 

- performance.service.ts

  //공연 목록 조회
  async findAll(category?: string): Promise<Performance[]> {
    const query = this.performanceRepository.createQueryBuilder('performance')
      .select(['performance.performanceId', 'performance.title', 'performance.category', 'performance.location', 'performance.price', 'performance.createdAt', 'performance.updatedAt'])
      .orderBy('performance.createdAt', 'DESC')
        
      if(category) {
        query.where('performance.category = :category', {category})
      }
      return await query.getMany();
      }
  • service단에서도 controller에서 넘어온 category를 findAll에 넣어주었다.
  • performanceRepository를 통해 performance 엔티티를 대상으로 QueryBuilder 인스턴스를 생성하도록했다.
  • **'performance'는 쿼리에서 이 엔티티를 참조할 때 사용할 별칭
  • 조건문을 통해 카테고리 값이 있으면, 쿼리에 카테고리 변수에 대한 조건을 추가해준다.
  • 쿼리를 실행하여 여러 개의 결과를 배열 형태로 반환한다.
  • **쿼리의 역할: 최종적으로 생성된 쿼리를 실행하고, 결과를 반환.

카테고리 필터링을 통한 예시 쿼리

SELECT performance.performanceId, performance.title, performance.category, performance.location, performance.price, performance.createdAt, performance.updatedAt
FROM performances performance
WHERE performance.category = 'CLASSIC'
ORDER BY performance.createdAt DESC

 

 

처음으로 쿼리를 사용해보았다. 어려운 부분 없이 단순한편인 것 같으나, 아직 Typescript와 Nest.js에 대한 미숙함때문에 만들어나가면서도 이해가 가지 않는 부분이 있었다. 그래도 자잘한 주석 하나하나 달아가며 금방 익숙해지기 위해 노력중이다. 빠르게 이것 외에도 새롭게 구현해보는 기능들을 정리해나가야겠다.

댓글