졸업 작품으로 운동 자세 분석 웹을 개발 중이다.
거의 막바지라 슬슬 시간이 생겼으니 방금 게시판 검색을 구현해서 한번 적어보려 한다.
요근래 계속 프론트만 만지다가 서버랑 DB 만지니 어려웠다.
아 그리고 저는 정석으로 Node 랑 서버를 공부 하지 않아서 '쟤 왜 저렇게 힘들게 구현하지 ?' 라고 생각하실 수도 있으니 제 방식이 별로라면 다른 블로그에서 봐주세요
아직 갈길이 먼 개발자 꿈나무 입니다..
게시판 포스팅이나 여타 다른 것들은 다 만들어 놨었고, 시간이 남아 내가 검색 기능을 구현한 것이다.
전체적으로 백엔드 부분은
이 분의 블로그를 많이 참조하였다.
이런 식으로 게시판 하단 pagination 위에 검색 form 을 추가해주었다.
<form method="get" onSubmit={onSubmit}>
<select value={options} onChange={selectHandle}>
<option value="title">제목</option>
<option value="body">내용</option>
<option value="title_body">제목+내용</option>
</select>
<input
type="text" name="searchText"
onChange={ handleChange }
value={value}
placeholder="검색어를 입력하세요"/>
<div>
<Cbutton type = "submit" onClick={onSubmit}>검색</Cbutton>
</div>
</form>
const onSubmit = (e) => {
e.preventDefault();
const { page } = qs.parse(location.search, {
ignoreQueryPrefix: true,
});
dispatch(searchPosts({ page: page, option: options, content: value }));
setValue('');
};
대충 프론트는 이런 식으로 짰다.
중요한 건 나는 querystring 을 통해 uri 에서 api/posts/search?option=title&content=아무거나검색할말
이런식으로 들어가게 할 거기 때문에 form 태그에서 method="get"을 지정해줘야한다.
그러면 자동으로 querystring을 쓰게끔 uri에 들어간다.
onSubmit에서 page 는 현재 주소에서 파싱해오고 dispatch 를 통해 searchPosts 함수에 page, option, content 를 보낸다.
export const searchPosts = ({ page, option, content }) => {
const queryString = qs.stringify({
page,
option,
content
});
return client.get(`/api/posts/search?${queryString}`);
}
frontend 에서 searchPosts 함수를 선언해준다.
get method로 주소는 아까 정한 것 처럼 /api/posts/search?${queryString} 으로 지정한다.
또한 qs.stringify를 써서 쿼리스트링 변환을 해준다.
이제 백엔드를 보자.
먼저 검색 기능을 쓰기 위해서는 인덱스가 만들어져야한다.
내 Post 스키마에서 title, body 를 검색하고 싶다면
PostSchema.index({ title: 'text', body: 'text' });
이런 식으로 스키마를 만드는 코드 맨 마지막에 이렇게 title과 body에 index를 만들어줘야 한다. (mongoose기준이다)
mongoDB에서는 createIndex 를 쓴다고 한다.
const posts = new Router();
... 중략
posts.get('/search', postsCtrl.searchPosts);
//GET /api/posts/search
export const searchPosts = async ctx => {
console.log(ctx.query);
const page = parseInt(ctx.query.page || '1', 10);
let options = [];
if(page < 1){
ctx.status = 400;
return;
}
try{
if(ctx.query.option == 'title'){
options = [{ title: new RegExp(ctx.query.content) }];
} else if(ctx.query.option == 'body'){
options = [{ body: new RegExp(ctx.query.content) }];
} else if(ctx.query.option == 'title_body'){
options = [{ title: new RegExp(ctx.query.content) }, { body: new RegExp(ctx.query.content) }];
} else {
const err = new Error('검색 옵션이 없습니다.');
err.status = 400;
throw err;
}
const posts = await Post.find({ $or: options })
.sort({ _id: -1 })
.limit(10)
.skip((page - 1) * 10)
.lean()
.exec();
const postCount = await Post.countDocuments(posts).exec();
ctx.set('Last-Page', Math.ceil(postCount / 10));
ctx.body = posts.map((post) => ({
...post,
body: removeHtmlAndShorten(post.body),
}));
} catch (e) {
ctx.throw(500, e);
}
};
postCount 는 pagination을 위한 코드고, 검색 기능에 중요한 코드는 ctx.query.option을 비교하는 부분이다.
아까 form 태그 안에서 select-option 태그가 가지고 있는 각 value 값과 검증을 한다.
사용자가 제목을 선택했다면 ctx.query.option => title이 들어가 있을 것이다.
즉 /api/posts/search?option=title 이렇게 queryString이 존재하는 것이다.
실제로 ctx.query 를 찍어보면
[0] [Object: null prototype] { option: 'title', content: 'kid' }
이런식으로 json 형태로 찍힌다.
page는 1페이지일 때 queryString에 없어서 만약 page가 따로 주어지지 않는다면 1을 default로 준다.
이제 options 배열에 우리가 검색할 조건들을 넣어줄 것이다.
if(ctx.query.option == 'title'){
options = [{ title: new RegExp(ctx.query.content) }];
} else if(ctx.query.option == 'body'){
options = [{ body: new RegExp(ctx.query.content) }];
} else if(ctx.query.option == 'title_body'){
options = [{ title: new RegExp(ctx.query.content) }, { body: new RegExp(ctx.query.content) }];
} else {
const err = new Error('검색 옵션이 없습니다.');
err.status = 400;
throw err;
}
RegExp 란게 보이는데 정규표현식 객체를 만드는 것이라고 한다.
'hi' 란 검색어를 찾고 싶다면 먼저 'hi'를 정규표현식 객체로 만들어야한다.
이제 여기까지 하면 options 배열을 가지고 find()만 해주면 된다.
const posts = await Post.find({ $or: options })
.sort({ _id: -1 })
.limit(10)
.skip((page - 1) * 10)
.lean()
.exec();
const postCount = await Post.countDocuments(posts).exec();
ctx.set('Last-Page', Math.ceil(postCount / 10));
ctx.body = posts.map((post) => ({
...post,
body: removeHtmlAndShorten(post.body),
}));
뭐가 거창해보이는데 중요한건 그냥 Post.find({ $or : options }) 요 부분이다.
or 옵션을 사용해 find 시 정말 말 그대로 options중에 하나만 만족해도 다 찾아준다.
어차피 제목, 내용, 제목+내용 이니까 or만 사용해주면 된다.
백엔드 코드도 다 짰으니 프론트에서 해보기 전에 Postman에서 한번 테스트 해보자
자 이렇게 Postman에서 Params의 Key Value - option, content 를 지정해주자
실제로는 form 태그에 의해 자동으로 된다.
결과를 보면 성공적으로 Post 스키마에서 title 안에 'kid'가 들어가는 데이터를 보여준다.
터미널에도 잘 찍힌다.
이제 프론트를 마저 잘 구현해서 실제로 검색이 되는 지 보면 된다.
제목으로 수고를 검색했다.
잘 된다.
title_body 옵션으로도 해보자
운동을 검색해봤다.
잘된다.
그럼 게시판 만드는 여러분 모두 힘내세요
'DEV > Node.js' 카테고리의 다른 글
[CI/CD] AWS CodeDeploy, CodePipeline 으로 node.js, ec2, git 배포 자동화하기 (4) | 2021.12.16 |
---|---|
[Node.js] Express, TypeScript, MongoDB 회원가입 (1) (3) | 2021.06.18 |
Node.js , express, mongoDB, typescript 초기 설정 (0) | 2021.05.26 |
Node.js + Koa + Typescript 로 슬랙 봇 개발해보기 (1) (0) | 2021.04.19 |
Node js + Express + Socket.io 로 1대 1 채팅 구현하기 (1) (4) | 2021.03.14 |