Search 기능 구현 전에 env 파일로 환경변수 관리하기
환경변수 설치
npm i dotenv
server.js 파일에 설치한 dotenv 불러오기
require('dotenv').config()
이후 server.js 와 같은 경로에 .env 파일 생성 후 아래와 같이 PORT와 DB_URL 작성하기 물론 변수 작명은 자유롭게 가능.
// .env 파일
PORT = '8080'
DB_URL = 'mongodb+srv........'
그리고 server.js에서 기존에 작성했던 PORT 번호와 DB_URL을 작성시엔 process.env.변수명으로 변경
process.env.PORT
process.env.DB_URL
Search 검색 기능
하나 찾을 때는 findOne()
여러개 찾을 때는 find(),toArray()
그래서 검색 요청을 할때는 POST 요청으로도 하지만 GET 요청으로 한다면 Query String으로 검색할 데이터를 함께 전송해서 찾을 수도 있다.
아래의 주소처럼 ? 뒤에 있는 것들이 Query String Parameter 이 되며
?데이터이름=데이터값 으로 구성되어 있다.
<https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=NodeJS>
그래서 list.ejs 파일에 검색 할 수 있는 태그를 만들고 JavaScript로 클릭시 input 태그에 있는 value를 새로 갱신한 주소인 /search?value=inputValue 값으로 넣어주기
<script>
$('#search').click((e) => {
// window.location.replace('/search?이름=값')
const search_data = $('#search_data').val()
window.location.replace('/search?value=' + search_data)
})
</script>
/search 주소로 get 요청을 했을 때 db 에서 해당 데이터를 가져오기 위해 server.js에 코드 작성하기.
그리고 Query String 값의 경우엔 req.query로 가져오고 우리가 value 로 작성했기에 req.query.value 로 값을 가져올 수 있다.
추가해서 find().toArray()로 값 여러개가 있을 경우 모두 가져올 수 있도록 했음.
app.get("/search", (req, res) => {
// console.log(req.query.value); // query string은 req.query 에 담겨있음.
db.collection("post")
.find({ 제목: req.query.value })
.toArray((err, data) => {
console.log(data);
});
});
숙제. 가져온 값을 보여주는 search.ejs 파일을 생성 후 조회된 데이터 보여주기
//server.js
app.get("/search", (req, res) => {
// console.log(req.query.value); // query string은 req.query 에 담겨있음.
db.collection("post")
.find({ 제목: req.query.value })
.toArray((err, data) => {
console.log(data);
res.render("search.ejs", { posts: data });
});
});
// search.ejs
<!doctype html>
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<link rel="stylesheet" href="/public/main.css">
<title>NodeJS+MongoDB</title>
</head>
<body>
<%- include('nav.html') %>
<h2 class="my-3 text-center my-5">검색 리스트</h2>
<div class="container">
<% for (var i=0; i< posts.length; i++) { %>
<div class="card my-3 py-2" style="width: 100%;">
<div class="card-body">
<h5 class="card-title"><%= posts[i]._id %> </h5>
<h6 class="card-subtitle mb-2 text-body-secondary">할일 제목: <%= posts[i].제목 %> </h6>
<p class="card-text">할일 마감날짜: <%= posts[i].날짜 %></p>
<button class="detail rounded-sm" data-id="<%= posts[i]._id %>">상세</button>
<button class="delete bg-danger text-white rounded-sm" data-id="<%= posts[i]._id %>">삭제</button>
</div>
</div>
<% } %>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script>
$('.detail').click((e) => {
location.href = '/detail/' + e.target.dataset.id
})
</script>
<script>
$('.delete').click((e) => {
// e.target = 지금 클릭한 것, this = 지금 이벤트 동작하는 곳
const id = e.target.dataset.id
// var target = $(this)
const target = $(e.target)
// JQuery를 사용해서 AJAX 요청
console.log(id);
$.ajax({
method: 'DELETE',
url: '/delete',
// 아래에서 _id의 값을 숫자 1로 전송을 해도 받아지는 데이터는 문자로 오게 된다.
data: {
_id: id
},
}).done((data) => {
// 요청 성공시 아래 코드 실행
console.log('성공');
// 페이지를 새로고침하면서 데이터 갱신 시키기.
// location.reload()
// `지금 현재 타겟의 부모중 li태그를 찾아라`라는 의미이며 fadeOut()으로 서서히 사라지게 해달라는 메서드를 추가
target.parent('div').parent('div').fadeOut()
}).fail((xhr, textStatus, errorThrown) => {
// xhr = xhr 안에 status, statusText, responseText 값들이 있어서 확인하면 됨, textStatus = 응답코드, errorThrown = 응답 메시지
// 실패시 여기로 즉 400 에러가 발생시 여기 코드가 나옴.
})
})
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct" crossorigin="anonymous">
</script>
</body>
</html>
여기서 문제점은 검색한 문자와 완벽히 똑같은것만 조회하기에
해당 문자가 들어간 모든 제목의 데이터를 가져오게 하고 싶다.
그렇게 하려면 여러 방법이 있는데
첫번째로는 정규식이 있다.
- /(슬래시) 안에 입력값을 넣어준다면 쉽게 적용된다.
- 다만 단점은 데이터가 여러개 있다면 find()로 찾게 될때 오래걸림.
//server.js
app.get("/search", (req, res) => {
// console.log(req.query.value); // query string은 req.query 에 담겨있음.
db.collection("post")
.find({ 제목: /반/ })
.toArray((err, data) => {
console.log(data);
res.render("search.ejs", { posts: data });
});
});
그래서 두번째 방법인 Indexing으로 찾기
- 각 리스트의 제목을 기준으로 정렬한 것을 기반으로 이진탐색 (Binary Search) 할 수 있도록 하기
- 현재 사용하고 있는 db인 Atlas MongoDB의 찾고자 하는 list가 있는 collection의 indexes에서 CREATE INDEX를 클릭 후 문자라면 type을 “text”, 숫자라면 type을 1 또는 -1 을 입력해주기.
Search Index
search index를 들어가기 전에 JavaScript로 Query String을 만들때 아래의 작성법 말고도
두가지 방법이 더 있다.
// 기존
window.location.replace('/search?value=' + 입력한값)
// jQuery + object 사용하기
const 자료 = {이름: '값', 나이: 100}
$.param(자료)
// Form 태그 사용시
// Form 태그안의 Input value를 모두 자동으로 query string으로 만들어줌
$(Form 태그).serialize()
이제 만들어둔 Search Index 사용하기
사용시 장점으로는
- 빠른 검색
- 다중 검색이 가능하다
- 다중 검색이라고 하면 검색창에 —> 안녕 반가워 라고 입력하게 되면
- 안녕이 들어간 제목과 반가워가 들어간 제목 모두 각각 찾아주게 된다.
- 제외하기
- 제외할 단어 앞에 -를 붙여주면 된다 (구글 검색법)
- ex) 안녕 -반가워
- 정확히 일치하는 것만
- “” 큰 따옴표안에 입력한 단어와 같은 것만 조회하게 된다.
// Search Index ==> 이진 탐색
app.get("/search", (req, res) => {
db.collection("post")
.find({ $text: { $search: req.query.value } }) // <------- 만들어둔 Index 사용하기 (검색 엔진과 같이 조회)
.toArray((err, data) => {
console.log(data);
res.render("search.ejs", { posts: data });
});
});
단점으로는
- 한글 친화적이지 않다.
- (띄어쓰기를 기준으로 저장하고 정렬하기에 단어의 일부만 조회하지 못한다.)
- 해결책
- Text-Index 사용하지 않기
- 검색할 문서의 양을 제한두고 조회해서 빠르게 찾기
- ex) 오늘부터 어느날 까지만 조회
- Text-Index 만들때 다르게 만들기 (띄어쓰기 단위로 Indexing 하지 않도록 하기)
- Atlas Search Index 만들기
- Atlas MongoDB의 collection에서 Search 를 클릭 후 Index 생성시 lucene을 lucene.korean 으로 변경해 ~을, ~를 같은 조사를 제거해주고 인덱싱해주기.
- Text-Index 사용하지 않기
강의에서는 Atlas Search Index를 만들어서 아래와 같이 코드를 작성하면 사용가능하며 다양한 연산자를 통해 조회 조건을 추가할 수 있다.
app.get("/search", (req, res) => {
const search_Condition = [
{
$search: {
index: "titleSearch",
text: {
query: req.query.value,
path: "제목", // 제목과 날짜 둘다 찾고 싶다면 ['제목', '날짜']
},
},
},
// 추가 검색 조건
{
$sort: { _id: -1 }, // 내림차순
// $sort: {_id: 1} 내림차순
},
// {
// $limit: 5 // 조회 게시물 제한
// }
// 그리고 기본적으로 정렬을 하지 않으면 searchScore로 검색어와의 유사도를 점수로 메겨서 그것을 기준으로 정렬하게 됨.
// {
// $project: { 제목: 1, _id: 0, score: { $meta: "searchScore"}} // 여기서 value로 1은 조건 추가, 0은 조건 제거를 의미
// }
];
db.collection("post")
.aggregate(search_Condition) // aggregate([{},{},{}]) 를 사용해서 배열 안의 여러개의 검색 조건을 넣을 수 있다. 예를 들어 어느 날짜, 어떤 단어, 정렬이요 등등
.toArray((err, data) => {
console.log(data);
res.render("search.ejs", { posts: data });
});
});
이번 search 에 관련해 작성한 코드 나열
// Full Scan 방식
// app.get("/search", (req, res) => {
// console.log(req.query.value); // query string은 req.query 에 담겨있음.
// db.collection("post")
// .find({ 제목: req.query.value })
// .toArray((err, data) => {
// console.log(data);
// res.render("search.ejs", { posts: data });
// });
// });
// Search Index ==> 이진 탐색
// app.get("/search", (req, res) => {
// db.collection("post")
// .find({ $text: { $search: req.query.value } }) // <------- 만들어둔 Index 사용하기 (검색 엔진과 같이 조회)
// .toArray((err, data) => {
// console.log(data);
// res.render("search.ejs", { posts: data });
// });
// });
// 한국어 친화적인 Search Index
app.get("/search", (req, res) => {
const search_Condition = [
{
$search: {
index: "titleSearch",
text: {
query: req.query.value,
path: "제목", // 제목과 날짜 둘다 찾고 싶다면 ['제목', '날짜']
},
},
},
// 추가 검색 조건
{
$sort: { _id: -1 }, // 내림차순
// $sort: {_id: 1} 내림차순
},
// {
// $limit: 5 // 조회 게시물 제한
// }
// 그리고 기본적으로 정렬을 하지 않으면 searchScore로 검색어와의 유사도를 점수로 메겨서 그것을 기준으로 정렬하게 됨.
// {
// $project: { 제목: 1, _id: 0, score: { $meta: "searchScore"}} // 여기서 value로 1은 조건 추가, 0은 조건 제거를 의미
// }
];
db.collection("post")
.aggregate(search_Condition) // aggregate([{},{},{}]) 를 사용해서 배열 안의 여러개의 검색 조건을 넣을 수 있다. 예를 들어 어느 날짜, 어떤 단어, 정렬이요 등등
.toArray((err, data) => {
console.log(data);
res.render("search.ejs", { posts: data });
});
});
'NodeJS+MongoDB' 카테고리의 다른 글
NodeJS+MongoDB Part3 - 게시판, API 관리 (0) | 2023.06.27 |
---|---|
NodeJS+MongoDB Part3 - 암호화 (0) | 2023.06.27 |
NodeJS+MongoDB Part3 - User (Session, JWT, OAuth) (0) | 2023.06.23 |
NodeJS+MongoDB Part2 + PUT (0) | 2023.06.23 |
NodeJS+MongoDB Part2 (0) | 2023.06.22 |