따꿍의 프로젝트

[2025.09.06] 검색 기능 구현 본문

웹프로젝트/졸작

[2025.09.06] 검색 기능 구현

공장 주인 따꿍 2025. 9. 6. 23:37

 

더미데이터

INSERT INTO Book
(title, genre, author, publisher, publish_date, summary, length, ISBN, bookmark_num)
VALUES
-- 제목 맨 앞 "과학..." → relevance 3
('과학의 역사', '과학', '김철수', '민음사', '2001-01-01', '과학 발전사', 300, '1111111111', 5),
('과학기술의 미래', '과학', '이영희', '사이언스북스', '2002-01-01', '과학과 기술의 전망', 280, '2222222222', 10),
('과학적 사고의 힘', '과학', '박민수', '까치', '2003-01-01', '과학적 사고법을 설명', 260, '3333333333', 8),
('과학과 종교', '과학', '최지훈', '문학사상', '2004-01-01', '과학과 종교의 대화', 240, '4444444444', 12),
('과학 혁명의 구조', '과학', '토머스 쿤', '사이언스북스', '2005-01-01', '패러다임 전환 설명', 400, '5555555555', 20),

-- 제목 중간 "...과학..." → relevance 2
('커피 한 잔의 과학', '취미', '박철수', '북하우스', '2020-11-11', '커피 맛을 과학적으로 설명', 220, '6666666666', 180),
('시간과 과학 이야기', '과학', '이영희', '사이언스북스', '2005-01-01', '시간과 과학을 다룬 책', 280, '7777777777', 10),
('음악 속의 과학', '예술', '홍길동', '음악출판사', '2015-05-05', '음악 현상을 과학적으로 설명', 310, '8888888888', 50),
('요리와 과학', '취미', '김지영', '푸드북스', '2018-09-09', '요리를 과학적으로 설명', 200, '9999999999', 70),
('영화 속의 과학', '예술', '최수현', '필름북스', '2017-03-03', '영화 장면 속 과학적 해석', 330, '1010101010', 90),
('자연 속의 과학', '과학', '박영호', '자연사', '2010-10-10', '자연현상을 과학적으로 설명', 280, '1111111112', 60),
('스포츠와 과학', '체육', '이준호', '스포츠북', '2012-12-12', '운동을 과학적으로 접근', 250, '1212121212', 40),
('패션과 과학', '예술', '김현정', '패션북', '2014-08-08', '패션에 숨어있는 과학 원리', 210, '1313131313', 30),
('심리학과 과학', '심리', '정유진', '심리북스', '2016-06-06', '심리학을 과학적으로 해석', 290, '1414141414', 25),
('문학 속의 과학', '문학', '오세훈', '문학사', '2011-11-11', '문학과 과학의 융합', 350, '1515151515', 15),

-- 저자명에 "과학" → relevance 1
('우주의 신비', '과학', '박과학', '까치', '2010-01-01', '우주와 과학적 발견', 400, '1616161616', 7),
('인류의 진화', '역사', '홍과학', '역사출판사', '2012-01-01', '인류 진화사', 370, '1717171717', 5),
('생명의 기원', '과학', '이과학', '생명사', '2013-03-03', '생명의 기원 설명', 390, '1818181818', 9),
('기후 변화', '과학', '최과학', '환경사', '2014-04-04', '지구 온난화와 기후변화', 320, '1919191919', 11),
('뇌와 마음', '과학', '정과학', '심리북스', '2015-05-05', '뇌 과학 연구', 280, '2020202020', 6),
('인공지능의 현재', '과학', 'AI과학', '테크북', '2018-08-08', 'AI 발전', 310, '2121212121', 14),
('바이러스의 비밀', '과학', '장과학', '메디북', '2019-09-09', '바이러스 연구', 260, '2222222222', 22),
('미래의 에너지', '과학', '신과학', '에너지북', '2020-10-10', '에너지 혁신', 330, '2323232323', 18),
('인터넷의 원리', '과학', '인터과학', 'IT북', '2021-11-11', '인터넷 기술', 300, '2424242424', 13),
('유전자와 삶', '과학', '유과학', '생명사', '2022-12-12', '유전자 과학', 350, '2525252525', 19);

 

 

controllers>search.ts

import { Request, Response, NextFunction } from 'express';
import { Op, Sequelize } from 'sequelize';
import Book from '../models/book';
import sequelize from '../sequelize';

export const searchBooks = async (
    req: Request,
    res: Response,
    next: NextFunction
) => {
    try {
        const q = (req.query.q as string | undefined)?.trim();
        const page = Math.max(1, Number(req.query.page ?? 1));
        const limit = 20;
        const offset = (page - 1) * limit;

        if (!q) {
            return res.status(400).json({
                success: false,
                message: '검색어가 필요합니다.',
            });
        }

        const likeAny = `%${q}%`;
        const likePrefix = `${q}%`;

        const relevanceLiteral = sequelize.literal(`
        CASE
            WHEN title LIKE ${sequelize.escape(likePrefix)} THEN 3
            WHEN title LIKE ${sequelize.escape(likeAny)}   THEN 2
            WHEN author LIKE ${sequelize.escape(likeAny)}  THEN 1
            ELSE 0
        END
    `);

        const { rows, count } = await Book.findAndCountAll({
            where: {
                [Op.or]: [
                    { title: { [Op.like]: likeAny } },
                    { author: { [Op.like]: likeAny } },
                ],
            },
            attributes: {
                include: [[relevanceLiteral, 'relevance']],
            },
            order: [
                [Sequelize.literal('relevance'), 'DESC'], //정확도 우선
                ['bookmark_num', 'DESC'], //동점이면 인기순
                ['book_id', 'DESC'], //또 동점이면 book_id 역순
            ],
            limit,
            offset,
        });

        const hasNext = offset + rows.length < count;
        const nextPage = hasNext ? page + 1 : null;

        return res.status(200).json({
            success: true,
            message: '도서 검색 결과입니다.',
            data: {
                items: rows,
                pagination: {
                    total: count,
                    page,
                    limit,
                    hasNext,
                    nextPage,
                    nextUrl: hasNext
                        ? `/search/books?q=${encodeURIComponent(q)}&page=${nextPage}`
                        : null,
                },
            },
        });
    } catch (err) {
        next(err);
    }
};

 

  • Sets pagination limit: max 20 books per page.
  • offset is how many rows to skip (e.g. if page=2, skip first 20).
  • likeAny and likePrefix is creating SQL LIKE patterns for searching.
    • likeAny = %q% → matches anywhere in the string.
    • likePrefix = q% → matches only if title starts with the query.

 

 

 

http://localhost:3000/search/books?q=과학

을 했는데

{
    "success": true,
    "message": "도서 검색 결과입니다.",
    "data": {
        "items": [
            {
                "book_id": 10,
                "title": "과학 혁명의 구조",
                "image_url": null,
                "genre": "과학",
                "author": "토머스 쿤",
                "translator": null,
                "publisher": "사이언스북스",
                "publish_date": "2005-01-01T00:00:00.000Z",
                "summary": "패러다임 전환 설명",
                "length": 400,
                "ISBN": "5555555555",
                "bookmark_num": 20,
                "pdf_url": null,
                "relevance": 3
            },
            {
                "book_id": 9,
                "title": "과학과 종교",
                "image_url": null,
                "genre": "과학",
                "author": "최지훈",
                "translator": null,
                "publisher": "문학사상",
                "publish_date": "2004-01-01T00:00:00.000Z",
                "summary": "과학과 종교의 대화",
                "length": 240,
                "ISBN": "4444444444",
                "bookmark_num": 12,
                "pdf_url": null,
                "relevance": 3
            },
            {
                "book_id": 7,
                "title": "과학기술의 미래",
                "image_url": null,
                "genre": "과학",
                "author": "이영희",
                "translator": null,
                "publisher": "사이언스북스",
                "publish_date": "2002-01-01T00:00:00.000Z",
                "summary": "과학과 기술의 전망",
                "length": 280,
                "ISBN": "2222222222",
                "bookmark_num": 10,
                "pdf_url": null,
                "relevance": 3
            },
            {
                "book_id": 8,
                "title": "과학적 사고의 힘",
                "image_url": null,
                "genre": "과학",
                "author": "박민수",
                "translator": null,
                "publisher": "까치",
                "publish_date": "2003-01-01T00:00:00.000Z",
                "summary": "과학적 사고법을 설명",
                "length": 260,
                "ISBN": "3333333333",
                "bookmark_num": 8,
                "pdf_url": null,
                "relevance": 3
            },
            {
                "book_id": 6,
                "title": "과학의 역사",
                "image_url": null,
                "genre": "과학",
                "author": "김철수",
                "translator": null,
                "publisher": "민음사",
                "publish_date": "2001-01-01T00:00:00.000Z",
                "summary": "과학 발전사",
                "length": 300,
                "ISBN": "1111111111",
                "bookmark_num": 5,
                "pdf_url": null,
                "relevance": 3
            },
            {
                "book_id": 11,
                "title": "커피 한 잔의 과학",
                "image_url": null,
                "genre": "취미",
                "author": "박철수",
                "translator": null,
                "publisher": "북하우스",
                "publish_date": "2020-11-11T00:00:00.000Z",
                "summary": "커피 맛을 과학적으로 설명",
                "length": 220,
                "ISBN": "6666666666",
                "bookmark_num": 180,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 15,
                "title": "영화 속의 과학",
                "image_url": null,
                "genre": "예술",
                "author": "최수현",
                "translator": null,
                "publisher": "필름북스",
                "publish_date": "2017-03-03T00:00:00.000Z",
                "summary": "영화 장면 속 과학적 해석",
                "length": 330,
                "ISBN": "1010101010",
                "bookmark_num": 90,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 14,
                "title": "요리와 과학",
                "image_url": null,
                "genre": "취미",
                "author": "김지영",
                "translator": null,
                "publisher": "푸드북스",
                "publish_date": "2018-09-09T00:00:00.000Z",
                "summary": "요리를 과학적으로 설명",
                "length": 200,
                "ISBN": "9999999999",
                "bookmark_num": 70,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 16,
                "title": "자연 속의 과학",
                "image_url": null,
                "genre": "과학",
                "author": "박영호",
                "translator": null,
                "publisher": "자연사",
                "publish_date": "2010-10-10T00:00:00.000Z",
                "summary": "자연현상을 과학적으로 설명",
                "length": 280,
                "ISBN": "1111111112",
                "bookmark_num": 60,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 13,
                "title": "음악 속의 과학",
                "image_url": null,
                "genre": "예술",
                "author": "홍길동",
                "translator": null,
                "publisher": "음악출판사",
                "publish_date": "2015-05-05T00:00:00.000Z",
                "summary": "음악 현상을 과학적으로 설명",
                "length": 310,
                "ISBN": "8888888888",
                "bookmark_num": 50,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 17,
                "title": "스포츠와 과학",
                "image_url": null,
                "genre": "체육",
                "author": "이준호",
                "translator": null,
                "publisher": "스포츠북",
                "publish_date": "2012-12-12T00:00:00.000Z",
                "summary": "운동을 과학적으로 접근",
                "length": 250,
                "ISBN": "1212121212",
                "bookmark_num": 40,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 18,
                "title": "패션과 과학",
                "image_url": null,
                "genre": "예술",
                "author": "김현정",
                "translator": null,
                "publisher": "패션북",
                "publish_date": "2014-08-08T00:00:00.000Z",
                "summary": "패션에 숨어있는 과학 원리",
                "length": 210,
                "ISBN": "1313131313",
                "bookmark_num": 30,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 19,
                "title": "심리학과 과학",
                "image_url": null,
                "genre": "심리",
                "author": "정유진",
                "translator": null,
                "publisher": "심리북스",
                "publish_date": "2016-06-06T00:00:00.000Z",
                "summary": "심리학을 과학적으로 해석",
                "length": 290,
                "ISBN": "1414141414",
                "bookmark_num": 25,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 20,
                "title": "문학 속의 과학",
                "image_url": null,
                "genre": "문학",
                "author": "오세훈",
                "translator": null,
                "publisher": "문학사",
                "publish_date": "2011-11-11T00:00:00.000Z",
                "summary": "문학과 과학의 융합",
                "length": 350,
                "ISBN": "1515151515",
                "bookmark_num": 15,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 12,
                "title": "시간과 과학 이야기",
                "image_url": null,
                "genre": "과학",
                "author": "이영희",
                "translator": null,
                "publisher": "사이언스북스",
                "publish_date": "2005-01-01T00:00:00.000Z",
                "summary": "시간과 과학을 다룬 책",
                "length": 280,
                "ISBN": "7777777777",
                "bookmark_num": 10,
                "pdf_url": null,
                "relevance": 2
            },
            {
                "book_id": 27,
                "title": "바이러스의 비밀",
                "image_url": null,
                "genre": "과학",
                "author": "장과학",
                "translator": null,
                "publisher": "메디북",
                "publish_date": "2019-09-09T00:00:00.000Z",
                "summary": "바이러스 연구",
                "length": 260,
                "ISBN": "2222222222",
                "bookmark_num": 22,
                "pdf_url": null,
                "relevance": 1
            },
            {
                "book_id": 30,
                "title": "유전자와 삶",
                "image_url": null,
                "genre": "과학",
                "author": "유과학",
                "translator": null,
                "publisher": "생명사",
                "publish_date": "2022-12-12T00:00:00.000Z",
                "summary": "유전자 과학",
                "length": 350,
                "ISBN": "2525252525",
                "bookmark_num": 19,
                "pdf_url": null,
                "relevance": 1
            },
            {
                "book_id": 28,
                "title": "미래의 에너지",
                "image_url": null,
                "genre": "과학",
                "author": "신과학",
                "translator": null,
                "publisher": "에너지북",
                "publish_date": "2020-10-10T00:00:00.000Z",
                "summary": "에너지 혁신",
                "length": 330,
                "ISBN": "2323232323",
                "bookmark_num": 18,
                "pdf_url": null,
                "relevance": 1
            },
            {
                "book_id": 26,
                "title": "인공지능의 현재",
                "image_url": null,
                "genre": "과학",
                "author": "AI과학",
                "translator": null,
                "publisher": "테크북",
                "publish_date": "2018-08-08T00:00:00.000Z",
                "summary": "AI 발전",
                "length": 310,
                "ISBN": "2121212121",
                "bookmark_num": 14,
                "pdf_url": null,
                "relevance": 1
            },
            {
                "book_id": 29,
                "title": "인터넷의 원리",
                "image_url": null,
                "genre": "과학",
                "author": "인터과학",
                "translator": null,
                "publisher": "IT북",
                "publish_date": "2021-11-11T00:00:00.000Z",
                "summary": "인터넷 기술",
                "length": 300,
                "ISBN": "2424242424",
                "bookmark_num": 13,
                "pdf_url": null,
                "relevance": 1
            }
        ],
        "pagination": {
            "total": 25,
            "page": 1,
            "limit": 20,
            "hasNext": true,
            "nextPage": 2,
            "nextUrl": "/search/books?q=%EA%B3%BC%ED%95%99&page=2"
        }
    }
}

아주 잘 돌아가는 것을 확인했다.