mongoose 에서 search 필드를 만들어 간단한 검색 지원하기

개요

MongoDB 에서 하나의 document 가 생성되거나 업데이트될 때마다, search 필드에 검색가능한 문자열을 저장하고, 추후 정규식을 이용하여 검색할 문자열이 포함되어 있는지 아닌지의 여부를 확인하는 방식을 통해 검색 기능을 구현해보도록 하겠습니다. 한글이 좀 더 원활하게 검색되게 하기 위해서 hangul-js 를 사용하여 한글을 낱낱히 흩어놓을 것입니다. 만약 검색해야 할 대상이 html 코드일 경우, HTML 태그와 같은 부분은 검색에 포함시킬 필요가 없으니까, html 에서 텍스트 내용만 가져오는 기능이 있는 string-strip-html 라이브러리도 사용해보도록 하겠습니다.

코드

// tool.js

const Hangul = require('hangul-js');

/**
 * 문자열 배열을 한글해체된 문자열 하나로 만들어주는 함수
 * @param {string[]} arr 문자열 배열
 * @returns {string} 한글.
 */
const searchArrToStr = (arr) =>
  Hangul.disassembleToString(arr.join('#').replace(/ /g, ''));

/**
 * doc 에서 검색 대상에 포함시킬 문자열을 리턴하는 함수
 * @callback SearchStrGetter
 * @param {MongooseDocument} doc 대상 문서
 * @returns {string} 검색 문자열
 */

/**
 * 해당 스키마에게 getter 를 이용해 search 기능을 만드는 함수
 * @param {Schema} schema
 * @param {string} searchField
 * @param {SearchStrGetter} getter
 */
const makeSchemaHaveSearchByGetter = (schema, searchField, getter) => {
  schema.pre('save', function () {
    this[searchField] = getter(this);
  });

  schema.post('updateOne', async function () {
    const docToUpdate = await this.model.findOne(this.getFilter());
    if (docToUpdate) await docToUpdate.save();
  });
}

module.exports = {
  searchArrToStr,
  makeSchemaHaveSearchByGetter
};
// schema.js

const mongoose = require('mongoose');
const { searchArrToStr, makeSchemaHaveSearchByGetter } = require('./tool.js');
const stripHtml = require('string-strip-html');

const post = new mongoose.Schema({
  // 생략
});

makeSchemaHaveSearchByGetter(post, 'search', (postDoc) => {
  const strArray = [];
  const { title = '', content = '' } = postDoc;
  strArray.push(title);
  strArray.push(stripHtml(content).result);
  return searchArrToStr(strArray);
});
// db.js

const postModel = require('생략');
module.exports = {
  getPosts(condition = {}) {
    const query = postModel.find();
    if (typeof condition.search === 'string' && condition.search !== '') {
      query.find({ search: new RegExp(search) });
    }
  },
};

tool.js 에서는 어떤 스키마에 대한 search 관련 string 을 만들어주는 헬퍼 함수를 제공하고, schema.js 에서는 스키마를 정의하면서 search 관련 기능을 추가하기 위해 tool.js 의 함수들을 호출하고, db.js 에서는 실제로 검색할 때 어떤 식으로 하는지를 보여줍니다.

흐름과 원리

검색 문자열 만드는 과정과 검색 실시하는 과정

Mongoose 미들웨어 활용하기

  schema.pre('save', function () {
    this[searchField] = getter(this);
  });

  schema.post('updateOne', async function () {
    const docToUpdate = await this.model.findOne(this.getFilter());
    if (docToUpdate) await docToUpdate.save();
  });

미들웨어에 대한 자세한 용법은 공식 문서를 참조해주세요. 사실 mongoose 미들웨어는 활용하기가 좀 까다롭습니다. mongoose 가 정확히 어떤 순서로 데이터를 저장하고 불러오는 지를 잘 알아야 활용도 잘 할 수 있기 때문입니다. 필자도 잘 아는 건 아니지만, save 와 updateOne 을 활용해서 생성/업데이트 상황을 반영하도록 했습니다. 우선 save일 경우 미리 searchField 를 설정하여 저장되도록 했고, updateOne 의 경우에는, 해당 프로세스가 완료된 후에 save()로 한번 더 호출하여 앞서 등록했던 save pre 미들웨어가 동작하도록 했습니다.

Mongoose 미들웨어에서는 this가 어떤 객체를 가리키고 있는지 확실히 알아야 잘 활용할 수 있습니다. 이 this를 활용해야 한다는 점 때문에 arrow function 을 쓰려고 하면 다소 골치아파집니다. (사실 필자는 시도해보지 않았습니다.)

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Scroll to top