MongoDB 셋팅하기 (무료 호스팅도 받아보자)
MongoDB 가입 후 Atlas에서 무료 호스팅으로 db를 열기 (과정은 따로 찾아보기)
이후 MongoDB에서 만든 db와 로컬에서 작업중인 server와 연결하기 위해 우선 mongodb 설치하기
// 강의와 같은 환경 구축을 위해 아래의 버전 설치 했다는 점.
$ npm install mongodb@3.6.4
그리고 MongoClient를 연결하기 위해 아래와 같이 코드 작성
// log를 작성한 이유는 그냥 서버 연결이 되었을 때 확인해보기 위해 작성했음.
const MongoClient = require("mongodb").MongoClient;
MongoClient.connect(
"mongodb+srv://admin:ghdals9578@cluster0.mhotj5h.mongodb.net/?retryWrites=true&w=majority",
(에러, client) => {
app.listen(8080, () => {
console.log("listening on 8080");
});
}
);
Database에 자료 저장하는 법 (한줄이면 끝)
우선 에러 처리를 위해 조건문으로 에러가 발생시 에러를 콘솔에 띄우도록 하는 코드
const MongoClient = require("mongodb").MongoClient;
MongoClient.connect(
"mongodb+srv://admin:ghdals9578@cluster0.mhotj5h.mongodb.net/?retryWrites=true&w=majority",
(에러, client) => {
if (에러) return console.log(에러);
app.listen(8080, () => {
console.log("listening on 8080");
});
}
);
그리고 데이터를 넣기 위해서 Collection을 만들어 줄텐데

위의 사진과 같이 Database라는 큰 공간에 있는 Collection 들로 구성되어 있다고 생각하면 된다.
예를들어 하나의 폴더인 Database에 여러개의 파일인 Collection으로!
그래서 아래와 같이 todoapp이라는 Database에 post라는 Collection을 하나 만들고 데이터 넣기

이후 아래와 같이 코드를 작성하고 저장하게 되면 사진과 같이 저장된걸 확인할 수 있다.
var db;
const MongoClient = require("mongodb").MongoClient;
MongoClient.connect(
"mongodb+srv://admin:ghdals9578@cluster0.mhotj5h.mongodb.net/?retryWrites=true&w=majority",
(에러, client) => {
if (에러) return console.log(에러);
db = client.db("todoapp"); // todoapp이라는 database에 연결하는 코드
// db에 내 이름과 나이를 저장하기
// db.collection('post').insertOne('저장할 데이터', (에러, 결과)=> {
// console.log('저장완료');
// })
// 아래 코드는 post라는 파일에 inserOne으로 저장하는데 데이터는 아래 작성한 객체형식의 데이터를 넣을거야 라는 의미
// 저장할 데이터는 객체형식으로 작성하기
db.collection("post").insertOne(
{ 이름: "admin", 나이: 10, _id: 0 },
(에러, 결과) => {
console.log("저장완료");
}
);
app.listen(8080, () => {
console.log("listening on 8080");
});
}
);

위와 같이 자동으로 타입도 명시해준다.
그리고 각 데이터 마다 _id가 꼭 필요한데 없이 작성하게 되면 자동으로 난수를 작성하게 된다.
만약 직접 부여하려고 한다면 아래와 같이 작성하면 된다.
db.collection("post").insertOne(
{ 이름: "admin", 나이: 10, _id: 0 },
(에러, 결과) => {
console.log("저장완료");
}
);
숙제
- 어떤 사람이 /add 라는 경로로 post 요청을 하면, 날짜와 제목의 2개의 데이터를 보내주는데 이때 ‘post’ 라는 이름을 가진 collection에 두개의 데이터를 저장하기!
- 형식은 object로 {제목: ‘’, 날짜: ‘’} 이렇게!
// 코드 작성은 connect 안에 적어도 되고 밖에 적어도 되는 것 같다!
// 우선 밖에 적어서 사용
app.post("/add", (req, res) => {
res.send("전송완료");
console.log(req.body.title);
console.log(req.body.date);
db.collection("post").insertOne(
{ 제목: req.body.title, 날짜: req.body.date, _id: 1 },
(에러, 결과) => {
console.log("저장완료");
}
);
});

데이터를 보다 편하게 넣기 위한 라이브러리 (EJS)
EJS 란?
JavaScript 코드를 HTML 문서에 삽입하여 동적 콘텐츠를 생성할 수 있도록 도와주는 JavaScript 템플릿 엔진
<!-- index.ejs -->
<html>
<body>
<h1>Hello, <%= name %>!</h1>
</body>
</html>
위의 예시에서 <%= name %> 부분은 EJS 코드로, name 변수의 값을 출력합니다. 서버 측에서는 EJS를 사용하여 동적으로 name 변수의 값을 변경하고 웹 페이지를 생성할 수 있습니다.
설치
npm i ejs

위의 사진과 같이 view engine을 ejs로 사용하겠다는 것을 명시해줘야 한다.

이렇게 html 확장자 대신 ejs 로 변경해도 된다.
이후 아래와 같이 작성하게 되면 서버에서 데이터를 가져오게 된다.

이런거는 React에서도 해당 변수에 데이터 바인딩을 해주는 거라서 사용하고 싶은거 사용하면 된다고 함.
그래서 아래 사진과 같이 server.js 에 /list로 입장시 데이터를 받게 하기 위한 코드를 작성

다만 위와 같은 에러가 발생하게 되는데
코드를 읽어보고 해결한다면 .ejs 파일들은 views 폴더 안에 있어야 하기에 아래와 같이 하기

이후 아래와 같이 mongoDB에 연결한 db의 collections의 post에 있는 모든 데이터를 가져오기 위한 코드 작성
app.get("/list", (req, res) => {
// DB에 있는 데이터를 꺼내려고 할 때는 어떤 데이터를 꺼낼지 작성해줘야 한다.
// 그래서 DB에 저장된 post라는 collection안의 어떠한 변수로 적힌 데이터를 꺼내달라고 코드를 작성해야함.
// 그리고 순서가 있는데 데이터를 꺼내고 ejs 파일을 불러오는게 순서가 맞으니 주의 해야함!
// db.collection('post').find() // toArray()를 안붙이면 메타데이터까지 다 가져오게 되기에 아래처럼 작성해주기.
db.collection('post').find().toArray((error, data) => {
console.log(data);
}); // 해당 db에 있는 모든 데이터를 가져오게 됨.
res.render("list.ejs");
});
/list 에 방문하게 되면 아래와 같이 데이터가 잘 가져오게 된다.

이제 console 에 보이는게 아니라 list.ejs로 데이터를 보내기 위해 아래와 같이 코드 작성하기
app.get("/list", (req, res) => {
// DB에 있는 데이터를 꺼내려고 할 때는 어떤 데이터를 꺼낼지 작성해줘야 한다.
// 그래서 DB에 저장된 post라는 collection안의 어떠한 변수로 적힌 데이터를 꺼내달라고 코드를 작성해야함.
// 그리고 순서가 있는데 데이터를 꺼내고 ejs 파일을 불러오는게 순서가 맞으니 주의 해야함!
// db.collection('post').find() // toArray()를 안붙이면 메타데이터까지 다 가져오게 되기에 아래처럼 작성해주기.
db.collection("post")
.find()
.toArray((error, data) => {
res.render("list.ejs", {posts: data}); // <------------- 요기
// 이렇게 db.collection 안에 render를 넣어줘야 데이터를 받아서 전달할 수 있음.
});
});
그러면 아래와 같이 list.ejs로 받아온 posts 배열의 하나를 정해서 원하는 object key 값을 작성해주면
<h4>할일 제목: <%= posts[0].제목 %> </h4>
<p>할일 마감날짜: <%= posts[0].날짜 %></p>

위와 같이 원하는 DB에 있는 데이터를 가져올 수 있게 된다.
그런데 위와 같이 하드코딩을 하게 되면 바보 너무 많은 코드를 작성해야하니
반복문을 사용할건데 ejs 문법으로 반복문을 작성하게 되면
// 기본적으로 JavaScript 문법으로 for 문을 작성하게 되면
// 아래와 같이 작성하게 된다.
for (i=0; i< posts.length; i++;) {
<h4>할일 제목: <%= posts[i].제목 %> </h4>
<p>할일 마감날짜: <%= posts[i].날짜 %></p>
}
// 여기서 ejs 문법으로 작성하게 되면
// 아래와 같이 <% 와 %>를 시작과 끝에 세트로 작성해줘야 한다고 한다. (드르브라...)
<% for (var i=0; i< posts.length; i++) { %>
<h4>할일 제목: <%= posts[i].제목 %> </h4>
<p>할일 마감날짜: <%= posts[i].날짜 %></p>
<% } %>

여기서 해당 데이터의 id값을 난수가 아닌 0,1,2,3 … 이렇게 하려고 하는데
이럴 경우 아래와 같이 생각할 수 있다.
id 값을 매기기 위해 총 데이터 개수를 가져오는 방법
단 예를 들어 하나의 데이터를 삭제했을 때 같은 id를 가진 새로운 데이터가 생기면 안되기에
기존의 id 값이 아닌 +1 한 id 값을 가져와야함.
위와 같은 문제를 해결하기 위해 1개의 collection을 더 만들어서 관리할 예정
그래서 기존 todoapp안에 post가 하나 있는데 여기에 counter라는 DataBase를 하나 더 만들어주고
INSERT DOCUMENT를 클릭해서 totalPost라고 하는 만들어진 총 게시글 개수와 name을 추가 해주기

그래서 코드를 아래와 같이 db.collection(’counter’).findOne({ 찾을Key: ‘찾을Value’}) 하면 찾아지게 된다.
app.post("/add", (req, res) => {
res.send("전송완료");
console.log(req.body.title);
console.log(req.body.date);
db.collection("post").insertOne(
// counter라는 collection에서 name: '게시물 개수'인 데이터를 찾아달라는 코드
// 여기 아래 코드!
db.collection("counter").findOne({ name: "게시물 개수" }, (error, data) => {
console.log(data.totalPost);
}),
);
});
그래서 게시물이 생성될때마다 해당 totalPost에서 +1 시켜준 값을 _id의 값으로 넣어주려면 아래와 같이 코드를 작성하면 된다.
app.post("/add", (req, res) => {
res.send("전송완료");
db.collection("counter").findOne({ name: "게시물 개수" }, (error, data) => {
const id = data.totalPost;
db.collection("post").insertOne(
{ 제목: req.body.title, 날짜: req.body.date, _id: id + 1 },
(에러, 결과) => {
console.log("저장완료");
}
);
});
});
findOne과 insertOne을 따로 분리해서 작성해줘서 넣어도 봤는데 찾기전에 처리를 해서인지 안돼서
순서대로 처리하도록 안에 작성해야 할 듯하다.
그리고 이제 가져온 totalPost의 값을 +1 된 값으로 수정해야하기에 아래와 같은 작업을 해야한다.
app.post("/add", (req, res) => {
res.send("전송완료");
db.collection("counter").findOne({ name: "게시물 개수" }, (error, data) => {
const id = data.totalPost;
db.collection("post").insertOne(
{ 제목: req.body.title, 날짜: req.body.date, _id: id + 1 },
(에러, 결과) => {
// 만약 여러개를 수정하겠다면 updateMany()
// db.collection('counter').updateOne({어떤데이터를 수정할지}, {operator : {변경할key : 수정값}}, ()=>{})
// operator ==> $set = 변경, $inc = 기존값에 더해줄 값, $min = 기존값보다 적을 때만 변경, $ rename = key값 이름 변경 ...
// 만약 딱히 처리할게 없다면 콜백함수를 지워도 됨
db.collection("counter").updateOne(
{ name: "게시물 개수" },
{ $inc: { totalPost: 1 } },
() => {
console.log("저장완료");
}
);
}
);
});
});
HTML 자체에서 DELETE 요청을 할 수가 없다.
<!-- 아래와 같이 HTML에서 요청할 수 있는 method는 GET, POST 두개 밖에 없다. -->
<form method="GET"></form>
<form method="POST"></form>
그래서 DELETE 요청할 수 있는 방법으로 두가지가 있다.
- method-override 라이브러리 사용
- AJAX 사용
- Asynchronous Javascript And Xml로 새로고침 없이 서버에 요청하는걸 도와주는 JS문법
AJAX를 편하게 사요하기 위해 JQuery로 실행하려고 함.
그래서 해당 delete 클래스가 있는 button 태그를 클릭시 ajax 메서드가 실행되도록 하며 data값인 id를 클릭한 버튼이 있는 id 데이터를 전달하기 위해 button 태그의 data-id 속성을 추가해 값으로 posts[i]의 _id 값을 전달해주기
- <% for (var i=0; i< posts.length; i++) { %>
- 할일 제목: <%= posts[i].제목 %>
- 할일 마감날짜: <%= posts[i].날짜 %> <% } %>
$('.delete').click((e) => {
const id = e.target.dataset.id
// JQuery를 사용해서 AJAX 요청
console.log(id);
$.ajax({
method: 'DELETE',
url: '/delete',
// 아래에서 _id의 값을 숫자 1로 전송을 해도 받아지는 데이터는 문자로 오게 된다.
data: {
_id: id
},
}).done((data) => {
// 요청 성공시 아래 코드 실행
console.log(data);
})
})
이후 server.js에 delete로 요청을 하면 요청한 곳에서 받아온 _id 값을 req.body._id 로 받을 수 있는데 여기서 data로 보낸 _id 값은 넘어오면서 문자로 변하기 때문에 JavaScript 문법인 parseInt 메서드로 데이터를 정수로 변경 시킨 후 해당 데이터를 제거해주기. **
app.delete("/delete", (req, res) => {
// req.body를 하게되면 ajax로 요청시 보낸 data를 받아서 확인할 수 있다.
// 그리고 아래처럼 req.body로 보낸 숫자 1의 데이터가 받아올때는 문자 '1'로 받아와지기에 Int로 변경 시켜주기.
const id = parseInt(req.body._id);
// req.body에 담겨온 게시물번호를 가진 글을 db에서 찾아서 삭제해주세요.
// deleteOne({어떤 항목을 삭제할지}, ()=>{}) --> 삭제 메서드
db.collection("post").deleteOne({ _id: id }, (error, data) => {
console.log("삭제완료");
});
});
그래서 요청이 성공했을 때는 res.status(200) 실패시 res.status(400) 을 전송하기
app.delete("/delete", (req, res) => {
// req.body를 하게되면 ajax로 요청시 보낸 data를 받아서 확인할 수 있다.
// 그리고 아래처럼 req.body로 보낸 숫자 1의 데이터가 받아올때는 문자 '1'로 받아와지기에 Int로 변경 시켜주기.
const id = parseInt(req.body._id);
// req.body에 담겨온 게시물번호를 가진 글을 db에서 찾아서 삭제해주세요.
// deleteOne({어떤 항목을 삭제할지}, ()=>{}) --> 삭제 메서드
db.collection("post").deleteOne({ _id: id }, (error, data) => {
console.log("삭제완료");
// 응답코드 200을 보내주기.
// .send({}) 로 메시지를 함께 보내주기.
if (error) {
res.status(400).send({ message: "실패" });
} else {
res.status(200).send({ message: "성공" });
}
});
});
<script>
$('.delete').click((e) => {
// e.target = 지금 클릭한 것, this = 지금 이벤트 동작하는 곳
const id = e.target.dataset.id
// var target = $(this)
var 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.parents('li').fadeOut()
}).fail((xhr, textStatus, errorThrown) => {
// xhr = xhr 안에 status, statusText, responseText 값들이 있어서 확인하면 됨, textStatus = 응답코드, errorThrown = 응답 메시지
// 실패시 여기로 즉 400 에러가 발생시 여기 코드가 나옴.
})
})
</script>

그리고 ${this}를 사용시 function을 사용하지 않고 arrow function을 사용하면 범위가 해당 함수 내에서만 작동하기에 주의 해야함.
<script>
// 범위를 벗어남
$('.delete').click((e) => {
const target = $(this)
})
// 아래 함수 범위
$('.delete').click(function(e) {
const target = $(this)
})
</script>
상세페이지 만들기
detail 페이지의 경우 parameter로 직접 작성한 주소가 아닌 클릭한 데이터의 고유 값에 따른 주소로 이동을 시켜줘야 하기에 nodeJS에서도 /detail/:id 와 같은 형식으로 경로를 작성할 수 있다.
// /detail로 접속하면 detail.ejs를 보여주기 (URL Parameter) ==> 각 상세페이지의 서로 다른 URL
app.get("/detail/:id", (req, res) => {
// :?? 를 작성하게 되면 parameter를 받아서 가변 주소를 가져오게 된다.
// res.render('detail.ejs', {이런 이름으로: 이런 데이터를 전송 가능})
});
이후 parameter로 받아온 값 예를 들어 /detail/:1로 들어갈 경우 마지막에 있는 1번에 해당하는 데이터를 db에서 조회후 보여주려고 하기에
위에서 했었던것처럼 db.collection("post").findOne( { _id: parseInt(req.params.id)} )를 사용해서 조회하기.
단 여기서 parseInt를 사용한 이유는 위에서 확인했던것처럼 req 데이터로 날라오는 값이 모두는 잘 모르겠지만 대부분이 string type으로 날라오기에 정수화 시켜줘야하기 때문.
그리고 해당 data가 있을 경우 detail.ejs 파일을 보여줄 수 있도록 하고, 없다면 error.ejs를 보여줄 수 있도록 하기.
// /detail로 접속하면 detail.ejs를 보여주기 (URL Parameter) ==> 각 상세페이지의 서로 다른 URL
app.get("/detail/:id", (req, res) => {
// :?? 를 작성하게 되면 parameter를 받아서 가변 주소를 가져오게 된다.
// res.render('detail.ejs', {이런 이름으로: 이런 데이터를 전송 가능})
// db에서 우리가 찾을 데이터의 _id에 해당하는 데이터를 가져와달라는 건데
// parameter의 값을 가져오려고 할 경우 req.params.?? 를 작성하게 되면 가져올 수 있다.
// 그리고 위에서 확인했던것처럼 req 데이터로 날라오는 값이 모두는 잘 모르겠지만 대부분이 string type으로 날라오기에 정수화 시켜주기!!!
db.collection("post").findOne(
{ _id: parseInt(req.params.id) },
(error, data) => {
if (data === null) {
console.log("데이터 없다.");
res.render("error.ejs");
} else {
res.render("detail.ejs", { data: data });
}
}
);
});
<!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">
<title>List</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Todo App</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Write</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Etc</a>
</li>
</ul>
</div>
</nav>
<h4>상세 페이지</h4>
<p class="font-weight-bolder text-primary"><%= data._id %> 번 게시물</p>
<p>제목: <%= data.제목 %></p>
<p>날짜: <%= data.날짜 %></p>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></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>
그리고 /list 페이지에서 각 list에 해당하는 데이터를 클릭 시 detail 페이지로 이동하려고 할 경우 우선은 a 태그를 이용해서 해당 경로로 이동시키는 것으로 해결했다.
<ul class="list-group">
<% for (var i=0; i< posts.length; i++) { %>
<li class="list-group-item" data-id="<%= posts[i]._id %>">
<a href="/detail/<%= posts[i]._id %>">
<h3><%= posts[i]._id %> </h3>
<p>할일 제목: <%= posts[i].제목 %> </p>
<p>할일 마감날짜: <%= posts[i].날짜 %></p>
</a>
<!-- data-id 속성으로 필요한 값 넘겨주기 -->
<button class="delete" data-id="<%= p만야ㄱ
+++ 수정
<% 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" data-id="<%= posts[i]._id %>">Detail</button>
<button class="delete" data-id="<%= posts[i]._id %>">삭제</button>
</div>
</div>
<% } %>
// 기존 a 태그가 보기 싫어서 JavaScript 문법인
// location을 사용해서 페이지를 이동시키도록 했음.
<script>
$('.detail').click((e) => {
location.href = '/detail/' + e.target.dataset.id
})
</script>
만약 css 파일을 사용하겠다면
예를 들어 public 폴더에 main.css 파일을 넣었다고 했을 경우
html 또는 ejs 파일에는 원래대로 head 태그 안에 아래와 같이 link 태그로 넣어주는데
<link rel="stylesheet" href="/public/main.css">
server.js 파일에도 작성을 해줘야 한다고 한다.
app.use('/public', express.static('public'))
(노션에 작성하고 옮기려니까 좀 귀찮구만...)
'NodeJS+MongoDB' 카테고리의 다른 글
NodeJS+MongoDB Part3 - 암호화 (0) | 2023.06.27 |
---|---|
NodeJS+MongoDB Part3 - Search (0) | 2023.06.26 |
NodeJS+MongoDB Part3 - User (Session, JWT, OAuth) (0) | 2023.06.23 |
NodeJS+MongoDB Part2 + PUT (0) | 2023.06.23 |
NodeJS+MongoDB Part1 (0) | 2023.06.04 |