찔끔찔끔씩😎

[Sopt] 7차 세미나(2) - Nodejs 검색 기능, 페이지네이션 본문

Server/Nodejs

[Sopt] 7차 세미나(2) - Nodejs 검색 기능, 페이지네이션

댕경 2022. 6. 18. 21:37
728x90


구현할 기능

  1. 특정 검색어에 해당하는 영화 검색 (search)
    1. title 에서 검색 (option)
    2. director 에서 검색 (option)
    3. title, director 에서 검색 (option)
  2. 페이지네이션
    • 두 개씩 페이지를 나눈다
    • 이때 클라이언트에게 마지막 페이지를 알려주기 위해 페이지 수도 함께 리턴해주자.

📍필요한 interfaces 들 추가해주기

interfaces/movie/MoviesResponseDto.ts

기존 MovieResponseDto에는 페이지 수를 넘겨줄 변수가 존재하지 않는다.

따라서 새로운 ResponseDto가 필요하다.

import { MovieInfo } from './MovieInfo';

export interface MoviesResponseDto{
    movies: MovieInfo[];
    lastPage: number;
}

interfaces/movie/MovieOptionType

검색시 조건을 담아둘 Dto를 만들어 준다. 우리는 세가지 조건을 사용할 것이기 때문에 아래와 같이 만들어준다.

export type MovieOptionType = 'title' | 'director' | 'title_director';

🔎 1. Controller  만들기

1. Search, Option

- req.query를 사용하여 받아온다.

- 위에서 받아온 option 이 지정한 option내에 있나 확인한다. (없다면 Bad Request)

2. Pagenation

- 페이지를 받아오거나, 없다면 1

 

3. 전체코드

/**
 * @route GET /movie?search=&option=&page=
 * @desc search movies
 * @access public
 */
const getMoviesBySearch = async (req: Request, res: Response) => {
    const { search,option } = req.query;
    const isOptionType = (option: string): option is MovieOptionType => {
        return ["title", "director", "title_director"].indexOf(option) !== -1;
    } //-1이면 저안에 없는 것임

    if (!isOptionType(option as string)) { // 우리가 정한 option이 아닌 넘이 넘어오면 Bad Request
        return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, message.NULL_VALUE));
    }

    const page: number = Number(req.query.page || 1);

    try{
        const data = await MovieService.getMoviesBySearch(search as string, option as MovieOptionType, page); // search 타입은 string
        
        res.status(statusCode.OK).send(
            util.success(statusCode.OK, message.SEARCH_MOVIE_SUCCESS, data));
    }catch(error){
        console.log(error);
        res.status(statusCode.INTERNAL_SERVER_ERROR).send(
            util.fail(
                statusCode.INTERNAL_SERVER_ERROR,
                message.INTERNAL_SERVER_ERROR,
            ),
        );

    }
}

🔎 2. Service 만들기

1. Search, Option

- 몽고 디비에서는 $regex, /.*string.*/을 사용한다. (cf. SQL에서는 LIKE 쓸 때 %string%)

- RegExp 생성자 방법은 let exp = new RegExp('abc') 처럼 사용한다.

- 정규 표현식 생성함수

const regex = (pattern: string) => new RegExp(`.*${pattern}.*`);

- 정규표현식 객체 생성 및 문자열 검색하기

const searchRegex: RegExp = regex(search); // 정규표현식 객체 생성
movies = await Movie.find({ title: { $regex: searchRegex } }) // $regex를 통해 정규표현식과 일치하는 문자열을 검색

2. Pagenation

- 해당 page에 해당하는 데이터 불러오기

perPage : 페이지 당 개수

sort : createAt 을 기준으로 -1 (최신순) 정렬

skip : 앞에서 부터 얼마나 건너뛸지

limit : 개수 제한

 

- 클라이언트에게 페이지 수 넘겨주기

Model.countDocuments({}) : 모든 도큐먼트 개수 반환

lastPage : 전체 document 개수 / 페이지 당 개수 (올림)

 

3. 전체코드

const getMoviesBySearch = async (
    search: string,
    option: MovieOptionType,
    page: number,
): Promise<MoviesResponseDto[]> => {
    const regex = (pattern: string) => new RegExp(`.*${pattern}.*`); // 몽고디비는 .*ㅇㄻㅇㄹ.* 을 사용하여 검색한다

    let movies: MovieInfo[] = [];
    
    const perPage: number = 2; // 페이지 당 데이터 수
    
    try {
        const searchRegex: RegExp = regex(search);

        if (option === 'title') {
            // title에서 검색
            movies = await Movie.find({ title: { $regex: searchRegex } })
                        .sort({createAt:-1}) // 최신순 정렬
                        .skip(perPage * (page-1)) // 만약 3번째 페이지를 검색하려면, 2 * 2 개를 건너뛰게한다.
                        .limit(perPage) // 한페이지 당 parPage개 씩 ;
        } else if (option === 'director') {
            // director에서 검색
            movies = await Movie.find({ director: { $regex: searchRegex } })
            .sort({createAt:-1}) // 최신순 정렬
            .skip(perPage * (page-1)) // 만약 3번째 페이지를 검색하려면, 2 * 2 개를 건너뛰게한다.
            .limit(perPage) // 한페이지 당 parPage개 씩 ;
        } else {
            // title, director 에서 검색
            movies = await Movie.find({
                $or: [
                    { director: { $regex: searchRegex } },
                    { title: { $regex: searchRegex } },
                ],
            })
            .sort({createAt:-1}) // 최신순 정렬
            .skip(perPage * (page-1)) // 만약 3번째 페이지를 검색하려면, 2 * 2 개를 건너뛰게한다.
            .limit(perPage) // 한페이지 당 parPage개 씩 ;
        }

        // 클라에게 넘겨줄 마지막 페이지도 계산해주자
        const total: number = await Movie.countDocuments({});
        const lastPage: number = Math.ceil(total/perPage);

        const data = {
            movies, 
            lastPage
        }

        return data;
    } catch (error) {
        console.log(error);
        throw error;
    }
};

🔎 3. Router 만들기

router.get('/',MovieController.getMoviesBySearch);

Comments