-
#6 MONGODB AND MONGOOSE
-
6.0 Array Database part One
-
6.1 Array Database part Two
-
6.2 Edit Viedo part One
-
6.3 Edit Video part Two
-
6.7 Introducing to MongoDB
-
6.8 Connecting to Mongo
-
6.9 CRUD Introduction
-
6.10 Video Model
-
6.11 Our First Query / 6.12 Our First Query part2 / 6.13 Async Await
-
6.14 Returns and Renders
-
6.15 Creating a Video part1
-
6.16 Creating a Video part2
-
6.17 Exceptions and Validation
-
-
6.18 More Schema
-
6.19 Video Detail
-
6.20 Edit Video part One
-
6.21 Edit Video part Two / 6.22 Edit Video part Three
-
6.23 Middlewares
-
6.24 Statics
-
6.25 Delete Video
-
6.26 Search part One / 6.27 Search part Two
front-end : HTML5, CSS3, Pug
back-end : NodeJS, MongoDB, Express
#6 MONGODB AND MONGOOSE
6.0 Array Database part One
mixin video(video)
div
a(href="/videos/" + video.id)=video.title
pug는 href, class, id 같은 attribute에는 #{}로 변수를 표시할 수 없다.
더하기 방식이나 백틱을 사용해서 표시해야한다.
export const watch = (req, res) => {
const { id } = req.params;
const video = videos[id - 1];
res.render("watch", { pageTitle: `Watching ${video.title}`, video });
};
id가 부여된 비디오 객체 데이터가 있다는 가정 하에 그 id를 받아서 해당 id의 비디오를 찾고 html렌더링하여 contents를 출력한다.
이때 video객체를 같이 보내서 해당 템플릿에 video 객체를 생성한다.
6.1 Array Database part Two
a(href="/video/edit") //localhost:4000/video/edit
a(href="video/edit") //localhost:4000/videos/video/edit
a(href=`${video.id}/edit`) //localhost:4000/videos/1/edit
href에서 /login를 사용하면 root + /login로 이동할 것이고 login을 사용하면 기존 url에 login만 붙은 url로 이동한다.
6.2 Edit Viedo part One
block content
h4 Change Title of video
form(method="POST")
input(name="title", placeholder="Video Title", value="video.title", required)
input(value="Save", type="submit)
Edit 템플릿에서 form태그를 사용해서 값을 입력하여 변경할 수 있게 해보자.
변경된 데이터를 저장하면 원래 form태그의 action속성에 적힌 url로 보내진다.
지금은 같은 url에 보내고 싶으니 action속성은 없이 method속성을 POST로 바꾸는 방식으로 한다.
(post는 action을 지정하지 않으면 그냥 기존 url 동일하게 보낸다.)
이때 서버가 post를 이해하지 못하기 때문에 알려줘야 한다.
라우터가 같은 url을 post하도록 만들고 postEdit 컨트롤러를 새로 생성한다.
videoRouter.get("/:id/edit", getEdit);
videoRouter.post("/:id/edit", postEdit)
( getEdit = 화면에 보여줌 / postEdit = 변경사항을 저장해줌 )
=> getEdit으로 보여진 화면에서 데이터를 변경하여 제출하면 같은 url로 post를 보내고 postEdit을 실행하게 된다.
get방식의 form은 뭔가를 검색할 때 사용한다. ( 검색어가 url에 포함됨 )
post방식은 파일을 보내거나 데이터베이스에 있는 값을 바꾸는 뭔가를 보낼 때 사용한다. ( url은 그대로 )
6.3 Edit Video part Two
videoRouter.route("/:id(\\d+)/edit").get(getEdit).post(postEdit)
라우터가 동일한 url을 get, post 시키게 하는 건 한줄로 정리 가능하다.
완성된 postEdit컨트롤러는 다음과 같다.
(input태그에 name='title' 빼먹지 않기!!)
export const postEdit = (req, res) => {
const { id } = req.params;
const { title } = req.body;
videos[id - 1].title = title;
return res.redirect(`videos/${id}`);
};
res.redirect("/videos/#{id}')를 사용하면 브라우저가 자동으로 이동하게 한다.
//server.js
app.use(express.urlencoded({extended: true}));
edit에서 수정한 정보는 req.body에 담겨져 있다.
req.body를 받기 위해서 form 태그를 이해시켜야 하는데 이를 위해서 express.urlencoded()사용한다.
6.7 Introducing to MongoDB
MongoDB
- 문서기반의 데이터베이스로 비교적 사용하기 쉽다.
6.8 Connecting to Mongo
Mongoose
- mongoDB와 node.js를 이어주는 패키지
- 자바스크립트를 써서 mongoDB를 사용할 수 있게 해준다.
- npm i mongoose로 설치한다.
db에 연결하기 위한 작업은 다음과 같다.
// src/db.js 파일 생성
import mongodb from "mongoose";
mongoose.connect("mongodb://127.0.0.1:27017/wetube", { //wetube는 db이름
useNewUrlParser: true,
useUnifiedTopology: true,
});
db연결을 위한 파일을 따로 생성하고 server.js에 import 시켜준다.
import 시킬 때는 파일 자체를 import 시킨다.
const db = mongoose.connection;
const handleOpen = () => console.log("Connected to DB");
const handleError = () => console.log("DB Error");
db.on("error", handleError); //db에 에러가 났을 때 이벤트
db.once("open", handleOpen); //db에 연결할 때 이벤트
on은 동작 발생 때마다 실행되는 것이고 once는 한 번만 실행되는 것이다.
6.9 CRUD Introduction
CRUD - create/read/update/delete
model은 애플리케이션의 데이터가 어떻게 생겼는지 mongoose를 통해 db에게 구조를 알려주는 역할이다.
그래서 model 폴더를 만들어 관리하는 게 편리할 것이다.
6.10 Video Model
video.js 파일 속에 video모델을 만들어보자.
우선 데이터 타입을 지정해주는 스키마를 만들고 해당 스키마를 토대로 모델을 생성한다.
import mongoose from "mongoose";
//데이터 타입을 지정해주는 스키마 생성
const videoSchema = new mongoose.Schema({
title: String,
description: String,
createdAt: Date,
hashtags: [{ type: String }],
meta: {
views: Number,
rating: Number,
},
});
//앞서 만든 스키마를 토대로 모델 생성
const Video = mongoose.model("Video", videoSchema);
export default Video;
db를 mongoose와 연결시켜서 video모델을 인식시키기 위해 모델을 server.js에 import한다.
반드시 db를 먼저 import하고 해야한다.
import 하여 모델을 우선적으로 컴파일 시키면 데이터 제어를 할 때 원할 때마다 불러오기에 용이하다.
6.11 Our First Query / 6.12 Our First Query part2 / 6.13 Async Await
앞서 server.js에 모델을 import 시킨 것은 서버와 직접적인 연관성이 있는 것이 아니기 때문에 init.js 파일을 따로 생성한 후 코드를 분리시킨다.
init.js
- 필요한 모든 것들을 import 시키는 역할을 담당한다.
- export 한 server.js까지 import한다.
server.js
- express 된 것과 서버의 configuration에 관련된 코드처리 담당한다.
스크립트 실행을 위해 꼭 package.json의 경로를 재설정해줘야 한다.
이제 실제 데이터베이스를 사용해보자.
Video 모델을 활용하는 방법은 두가지가 있다. (Video.find())
데이터베이스를 불러오기 위해서 find()함수를 사용하는데 이 함수는 실행시간이 조금 걸린다.
데이터베이스를 불러오지 못한 상태에서 렌더링 하면 에러가 발생할 수 있으므로 완벽히 불러온 후 렌더링 시키는 것이 중요하다.
이를 위한 방법은 두가지가 있다.
1. callback 함수를 사용한다.
//videoController.js
import Video from "../models/Video"
export const home = (req, res) => {
Video.find({}, (error, videos) => {
res.render("home", { pageTitle: "Home", videos: [] });
});
};
find()함수는 두개의 인자를 갖는다.
첫번째 인자는 search terms로 찾는 데이터 형식을 말한다. 이게 비어있으면 모든 형식을 찾는다는 것을 뜻한다.
두번째 인자는 callback함수로 err와 docs값을 불러올 수 있다.
콜백함수 내부에 리턴을 하여 find()를 통한 데이터베이스 검색이 끝나고 렌더링하게 한다.
콜백함수는 에러를 바로 불러올 수 있게 할 수 있다는 장점이 있지만, 함수 안에 함수를 써야 한다는 점이 걸린다.
이 문제를 해결한 방법이 promise이다.
2. promise
export const home = async (req, res) => {
try {
const videos = await Video.find({});
res.render("home", { pageTitle: "Home", videos });
} catch {
return res.render("server-error");
}
};
async - await 방식은 굉장히 직관적이다. js가 어디서 기다리는 지 알 수 있다.
await는 반드시 async 안에서 사용해야 하고, 에러는 try-catch문을 따로 사용해서 잡아내야 한다.
6.14 Returns and Renders
return은 함수를 에러없이 마무리 짓는 느낌으로 사용된다.
return이 없어도 코드는 정상 작동된다.
res.render / res.end / res.redirect과 같은 함수는 중복으로 사용 불가능하다.
사용할 경우 에러가 발생한다.
6.15 Creating a Video part1
새로운 비디오 객체를 생성해보자.
video 스키마에서 title, description, hashtags에 해당하는 값만 사용자가 직접 주면 나머지는 시스템이 알아서 부여하도록 할 것이다.
해당 값을 얻을 수 있도록 input 태그를 추가해준 다음 postUpload 컨트롤러를 다음과 같이 수정한다.
export const postUpload = (req, res) => {
const { title, description, hashtags } = req.body;
const video = new Video({
title,
description,
createdAt: Date.now(),
hashtags: hashtags.split(",").map((word) => `#${word}`),
meta: {
views: 0,
rating: 0,
},
});
}
6.16 Creating a Video part2
Mongoose는 스키마에서 지정한 데이터 타입에 맞는 객체를 생성할 수 있도록 도와준다.
잘못된 데이터 타입을 입력할 경우 해당 데이터는 아예 포함시키지 않는다.
앞서 생성한 객체를 실제 데이터베이스에 저장해보자.
Video는 mongoose 모델인 덕분에 save()함수를 사용하면 데이터베이스에 쉽게 저장할 수 있다. save()는 프로미스를 리턴하기 때문에 save() 실행이 완료될 때까지 기다려줘야 한다. 이는 async-await를 사용하여 해결한다.
export const postUpload = async (req, res) => {
const { title, description, hashtags } = req.body;
const video = new Video({
...
});
const dbVideo = await video.save();
return res.redirect("/");
};
데이터베이스에 데이터를 저장하는 또다른 간단한 방법이 있다.
export const postUpload = async (req, res) => {
const { title, description, hashtags } = req.body;
await Video.create({
...
});
return res.redirect("/");
};
이 방법은 시스템이 객체를 알아서 만들어주고 바로 데이터베이스에 저장할 수 있도록 해준다. 에러 발생을 잡기 위해서 try catch문을 사용해준다.
6.17 Exceptions and Validation
데이터타입은 상세하게 지정해줄수록 좋다. 꼭 필요한 데이터 값이 있다면 스키마 required를 설정해주는 것이 좋다.
required가 true로 설정된 데이터는 데이터가 빠졌을 경우 에러를 발생시킨다.
default를 지정해준 데이터타입은 데이터가 빠졌어도 default값으로 저장된다.
const videoSchema = new mongoose.Schema({
title: { type: String, required: true },
description: { type: String, required: true },
createdAt: { type: Date, required: true, default: Date.now() },
hashtags: [{ type: String }],
meta: {
views: { type: Number, required: true, default: 0 },
rating: { type: Number, required: true, default: 0 },
},
});
데이터 타입으로 인한 에러가 발생했을 경우 try-catch문으로 에러를 잡아줄 수 있고 해당 에러 메세지를 포함시켜 렌더링 시키면 사용자에게 에러메세지를 보여줄 수 있다.
export const postUpload = async (req, res) => {
const { title, description, hashtags } = req.body;
try {
await Video.create({
...
});
return res.redirect("/");
} catch (error) {
return res.render("upload", {
pageTitle: "Upload Video",
errorMessage: error._message,
});
}
};
6.18 More Schema
https://mongoosejs.com/docs/schematypes.html
Mongoose v6.7.2: SchemaTypes
SchemaTypes handle definition of path defaults, validation, getters, setters, field selection defaults for queries, and other general characteristics for Mongoose document properties. You can think of a Mongoose schema as the configuration object for a Mon
mongoosejs.com
몽구스에서 지원하는 스키마 옵션을 확인할 수 있다.
데이터의 길이를 제한하는 max/minlength 옵션을 사용할 경우에는 input 태그에도 해당 속성을 주어야 한다.
6.19 Video Detail
moogodb에서 자동으로 설정해주는 id값은 16진수로 이루어진 24자리의 string값이다
하지만 우리는 라우터에서 id가 숫자로 이루어져있을 경우에만 watch 컨트롤러를 불러오게 제한을 두었기 때문에 에러가 발생한다.
videoRouter.get("/:id(\\d+)", watch);
이를 해결하기 위해서 정규식을 사용한다.
videoRouter.get("/:id([0-9a-f]{24})", watch);
해당 정규식은 0-9 , a-f로 이루어진 24자리 string이라는 뜻이다.
이제 watch 컨트롤러에서 id를 통해 video를 불러와보자.
몽구스에서 지원하는 model 관련 함수인 findOne()과 findById()는 특정 객체를 찾는데 도움을 준다.
findOne()은 조회수 같은 모든 condition을 조건으로 보내 조건에 맞는 객체를 찾아준다.
findById()는 id로 객체를 찾아준다.
export const watch = async (req, res) => {
const { id } = req.params;
const video = await Video.findById(id);
res.render("watch", { pageTitle: video.title, video });
};
findById()를 사용하여 watch 컨트롤러를 수정해준다.
6.20 Edit Video part One
존재하지 않는 비디오 상세 페이지로 접근하는 것을 방지하기 위하여 watch 컨트롤러에서 video가 존재하지 않을 경우 404템플릿을 렌더링시킬 수 있게 한다.
이제 비디오 수정 기능을 구현해보자.
원하는 비디오를 가져오기 위하여 getEdit 컨트롤러를 수정한다 .
export const getEdit = async (req, res) => {
const { id } = req.params;
const video = await Video.findById(id);
if (video === null) {
return res.render("404", { pageTitle: "Video not found" });
}
res.render("edit", { pageTitle: `Editing: ${video.title} `, video });
};
id를 통해 수정하고 싶은 비디오를 가져오는 방법이 앞서 했던 방식과 동일하다.
6.21 Edit Video part Two / 6.22 Edit Video part Three
이제 수정한 내용을 다시 저장해보도록 하자.
export const postEdit = async (req, res) => {
const { id } = req.params;
const { title, description, hashtags } = req.body;
const video = await Video.findById(id);
if (video === null) {
return res.render("404", { pageTitle: "Video not found" });
}
video.title = title;
return res.redirect(`/videos/${id}`);
};
이렇게 직접 하나하나 값을 다시 저장시키는 방법도 있지만 더 간편한 방법이 있다.
몽구스에서 지원하는 함수인 findByIdAndUpdate()를 사용하여 간단하게 데이터베이스를 업데이트 시킬 수 있다.
export const postEdit = async (req, res) => {
const { id } = req.params;
const { title, description, hashtags } = req.body;
const video = await Video.exists({ _id: id });
if (video === null) {
return res.render("404", { pageTitle: "Video not found" });
}
await Video.findByIdAndUpdate(id, {
title,
description,
hashtags: hashtags
.split(",")
.map((word) => (word.startsWith("#") ? word : `#${word}`)),
});
return res.redirect(`/videos/${id}`);
};
6.23 Middlewares
몽구스에서 미들웨어를 만들어보자. 몽구스는 다양한 미들웨어를 지원한다.
미들웨어는 반드시 모델 생성 전에 만들어져야 하고 미들웨어를 선언할 때는 pre()를 사용한다.
videoSchema.pre("save", async function () {
this.hashtags = this.hashtags[0]
.split(",")
.map((word) => (word.startsWith("#") ? word : `#${word}`));
});
save 미들웨어에서 this는 저장되는 문서를 가리킨다. function()은 save되기 전에 실행되기 때문에 데이터베이스에 저장되기 전 데이터 처리를 할 수 있다.
6.24 Statics
findByIdAndUpdate() 에서는 save훅업이 발생하지 않기 때문에 앞서 설명한 미들웨어 적용이 불가능하다. 미들웨어 대신 직접 함수를 만들어 데이터를 변경시켜줄 수 있다.
//Video.js
export const formatHashtags = (hashtags) =>
hashtags.split(",").map((word) => (word.startsWith("#") ? word : `#${word}`));
//videoController.js
import Video, { formatHashtags } from "../models/Video";
hashtags: formatHashtags(hashtags)
몽구스에서 지원하는 모델 function()을 직접 만들어 줄 수 있는데 이걸 static이라고 한다. static은 모델에 자동으로 포함되는 함수기 때문에 import 할 필요도 없다.
//Video.js
videoSchema.static("formatHashtags", function (hashtags) {
return hashtags
.split(",")
.map((word) => (word.startsWith("#") ? word : `#${word}`));
});
//videoController.js
hashtags: Video.formatHashtags(hashtags)
6.25 Delete Video
특정 비디오를 데이터베이스에서 삭제하는 방법은 findByIdAndDelete() 함수를 사용하여 간단하게 할 수 있다.
//videoRouter.js
videoRouter.get("/:id([0-9a-f]{24})/delete", deleteViedeo);
//videoController.js
export const deleteViedeo = async (req, res) => {
const { id } = req.params;
await Video.findByIdAndDelete(id);
return res.redirect("/");
};
6.26 Search part One / 6.27 Search part Two
키워드를 통해 영상 검색하는 기능을 구현해보자.
export const search = async (req, res) => {
const { keyword } = req.query;
let videos = [];
if (keyword) {
videos = await Video.find({
title: {
$regex: new RegExp(`${keyword}$`, "i"),
},
});
}
return res.render("search", { pageTitle: "Search", videos });
req.query를 불러오면 url에 있는 모든 정보를 얻을 수 있다. get 방식으로 접근하여 keyword값이 url에 포함되어 있기 때문에 req.query를 통해 가져온다.
모델의 find() 함수를 사용하여 제목에 해당 키워드를 가진 영상을 찾을 수 있는데 이때 RegExp를 사용하면 더 다양하게 찾을 수 있다.
RegExp는 MongoDB가 지원하는 기능으로 패턴을 사용해 텍스트를 판별해준다.
'강의기록' 카테고리의 다른 글
[유튜브 클론코딩] #5 TEMPLATES (0) | 2022.05.17 |
---|---|
[유튜브 클론코딩] #4 ROUTERS (0) | 2022.05.17 |
[유튜브 클론코딩] #3 Introduction To EXPRESS (0) | 2022.05.17 |
[유튜브 클론코딩] #1 #2 Setup (0) | 2022.05.17 |
front-end : HTML5, CSS3, Pug
back-end : NodeJS, MongoDB, Express
#6 MONGODB AND MONGOOSE
6.0 Array Database part One
mixin video(video)
div
a(href="/videos/" + video.id)=video.title
pug는 href, class, id 같은 attribute에는 #{}로 변수를 표시할 수 없다.
더하기 방식이나 백틱을 사용해서 표시해야한다.
export const watch = (req, res) => {
const { id } = req.params;
const video = videos[id - 1];
res.render("watch", { pageTitle: `Watching ${video.title}`, video });
};
id가 부여된 비디오 객체 데이터가 있다는 가정 하에 그 id를 받아서 해당 id의 비디오를 찾고 html렌더링하여 contents를 출력한다.
이때 video객체를 같이 보내서 해당 템플릿에 video 객체를 생성한다.
6.1 Array Database part Two
a(href="/video/edit") //localhost:4000/video/edit
a(href="video/edit") //localhost:4000/videos/video/edit
a(href=`${video.id}/edit`) //localhost:4000/videos/1/edit
href에서 /login를 사용하면 root + /login로 이동할 것이고 login을 사용하면 기존 url에 login만 붙은 url로 이동한다.
6.2 Edit Viedo part One
block content
h4 Change Title of video
form(method="POST")
input(name="title", placeholder="Video Title", value="video.title", required)
input(value="Save", type="submit)
Edit 템플릿에서 form태그를 사용해서 값을 입력하여 변경할 수 있게 해보자.
변경된 데이터를 저장하면 원래 form태그의 action속성에 적힌 url로 보내진다.
지금은 같은 url에 보내고 싶으니 action속성은 없이 method속성을 POST로 바꾸는 방식으로 한다.
(post는 action을 지정하지 않으면 그냥 기존 url 동일하게 보낸다.)
이때 서버가 post를 이해하지 못하기 때문에 알려줘야 한다.
라우터가 같은 url을 post하도록 만들고 postEdit 컨트롤러를 새로 생성한다.
videoRouter.get("/:id/edit", getEdit);
videoRouter.post("/:id/edit", postEdit)
( getEdit = 화면에 보여줌 / postEdit = 변경사항을 저장해줌 )
=> getEdit으로 보여진 화면에서 데이터를 변경하여 제출하면 같은 url로 post를 보내고 postEdit을 실행하게 된다.
get방식의 form은 뭔가를 검색할 때 사용한다. ( 검색어가 url에 포함됨 )
post방식은 파일을 보내거나 데이터베이스에 있는 값을 바꾸는 뭔가를 보낼 때 사용한다. ( url은 그대로 )
6.3 Edit Video part Two
videoRouter.route("/:id(\\d+)/edit").get(getEdit).post(postEdit)
라우터가 동일한 url을 get, post 시키게 하는 건 한줄로 정리 가능하다.
완성된 postEdit컨트롤러는 다음과 같다.
(input태그에 name='title' 빼먹지 않기!!)
export const postEdit = (req, res) => {
const { id } = req.params;
const { title } = req.body;
videos[id - 1].title = title;
return res.redirect(`videos/${id}`);
};
res.redirect("/videos/#{id}')를 사용하면 브라우저가 자동으로 이동하게 한다.
//server.js
app.use(express.urlencoded({extended: true}));
edit에서 수정한 정보는 req.body에 담겨져 있다.
req.body를 받기 위해서 form 태그를 이해시켜야 하는데 이를 위해서 express.urlencoded()사용한다.
6.7 Introducing to MongoDB
MongoDB
- 문서기반의 데이터베이스로 비교적 사용하기 쉽다.
6.8 Connecting to Mongo
Mongoose
- mongoDB와 node.js를 이어주는 패키지
- 자바스크립트를 써서 mongoDB를 사용할 수 있게 해준다.
- npm i mongoose로 설치한다.
db에 연결하기 위한 작업은 다음과 같다.
// src/db.js 파일 생성
import mongodb from "mongoose";
mongoose.connect("mongodb://127.0.0.1:27017/wetube", { //wetube는 db이름
useNewUrlParser: true,
useUnifiedTopology: true,
});
db연결을 위한 파일을 따로 생성하고 server.js에 import 시켜준다.
import 시킬 때는 파일 자체를 import 시킨다.
const db = mongoose.connection;
const handleOpen = () => console.log("Connected to DB");
const handleError = () => console.log("DB Error");
db.on("error", handleError); //db에 에러가 났을 때 이벤트
db.once("open", handleOpen); //db에 연결할 때 이벤트
on은 동작 발생 때마다 실행되는 것이고 once는 한 번만 실행되는 것이다.
6.9 CRUD Introduction
CRUD - create/read/update/delete
model은 애플리케이션의 데이터가 어떻게 생겼는지 mongoose를 통해 db에게 구조를 알려주는 역할이다.
그래서 model 폴더를 만들어 관리하는 게 편리할 것이다.
6.10 Video Model
video.js 파일 속에 video모델을 만들어보자.
우선 데이터 타입을 지정해주는 스키마를 만들고 해당 스키마를 토대로 모델을 생성한다.
import mongoose from "mongoose";
//데이터 타입을 지정해주는 스키마 생성
const videoSchema = new mongoose.Schema({
title: String,
description: String,
createdAt: Date,
hashtags: [{ type: String }],
meta: {
views: Number,
rating: Number,
},
});
//앞서 만든 스키마를 토대로 모델 생성
const Video = mongoose.model("Video", videoSchema);
export default Video;
db를 mongoose와 연결시켜서 video모델을 인식시키기 위해 모델을 server.js에 import한다.
반드시 db를 먼저 import하고 해야한다.
import 하여 모델을 우선적으로 컴파일 시키면 데이터 제어를 할 때 원할 때마다 불러오기에 용이하다.
6.11 Our First Query / 6.12 Our First Query part2 / 6.13 Async Await
앞서 server.js에 모델을 import 시킨 것은 서버와 직접적인 연관성이 있는 것이 아니기 때문에 init.js 파일을 따로 생성한 후 코드를 분리시킨다.
init.js
- 필요한 모든 것들을 import 시키는 역할을 담당한다.
- export 한 server.js까지 import한다.
server.js
- express 된 것과 서버의 configuration에 관련된 코드처리 담당한다.
스크립트 실행을 위해 꼭 package.json의 경로를 재설정해줘야 한다.
이제 실제 데이터베이스를 사용해보자.
Video 모델을 활용하는 방법은 두가지가 있다. (Video.find())
데이터베이스를 불러오기 위해서 find()함수를 사용하는데 이 함수는 실행시간이 조금 걸린다.
데이터베이스를 불러오지 못한 상태에서 렌더링 하면 에러가 발생할 수 있으므로 완벽히 불러온 후 렌더링 시키는 것이 중요하다.
이를 위한 방법은 두가지가 있다.
1. callback 함수를 사용한다.
//videoController.js
import Video from "../models/Video"
export const home = (req, res) => {
Video.find({}, (error, videos) => {
res.render("home", { pageTitle: "Home", videos: [] });
});
};
find()함수는 두개의 인자를 갖는다.
첫번째 인자는 search terms로 찾는 데이터 형식을 말한다. 이게 비어있으면 모든 형식을 찾는다는 것을 뜻한다.
두번째 인자는 callback함수로 err와 docs값을 불러올 수 있다.
콜백함수 내부에 리턴을 하여 find()를 통한 데이터베이스 검색이 끝나고 렌더링하게 한다.
콜백함수는 에러를 바로 불러올 수 있게 할 수 있다는 장점이 있지만, 함수 안에 함수를 써야 한다는 점이 걸린다.
이 문제를 해결한 방법이 promise이다.
2. promise
export const home = async (req, res) => {
try {
const videos = await Video.find({});
res.render("home", { pageTitle: "Home", videos });
} catch {
return res.render("server-error");
}
};
async - await 방식은 굉장히 직관적이다. js가 어디서 기다리는 지 알 수 있다.
await는 반드시 async 안에서 사용해야 하고, 에러는 try-catch문을 따로 사용해서 잡아내야 한다.
6.14 Returns and Renders
return은 함수를 에러없이 마무리 짓는 느낌으로 사용된다.
return이 없어도 코드는 정상 작동된다.
res.render / res.end / res.redirect과 같은 함수는 중복으로 사용 불가능하다.
사용할 경우 에러가 발생한다.
6.15 Creating a Video part1
새로운 비디오 객체를 생성해보자.
video 스키마에서 title, description, hashtags에 해당하는 값만 사용자가 직접 주면 나머지는 시스템이 알아서 부여하도록 할 것이다.
해당 값을 얻을 수 있도록 input 태그를 추가해준 다음 postUpload 컨트롤러를 다음과 같이 수정한다.
export const postUpload = (req, res) => {
const { title, description, hashtags } = req.body;
const video = new Video({
title,
description,
createdAt: Date.now(),
hashtags: hashtags.split(",").map((word) => `#${word}`),
meta: {
views: 0,
rating: 0,
},
});
}
6.16 Creating a Video part2
Mongoose는 스키마에서 지정한 데이터 타입에 맞는 객체를 생성할 수 있도록 도와준다.
잘못된 데이터 타입을 입력할 경우 해당 데이터는 아예 포함시키지 않는다.
앞서 생성한 객체를 실제 데이터베이스에 저장해보자.
Video는 mongoose 모델인 덕분에 save()함수를 사용하면 데이터베이스에 쉽게 저장할 수 있다. save()는 프로미스를 리턴하기 때문에 save() 실행이 완료될 때까지 기다려줘야 한다. 이는 async-await를 사용하여 해결한다.
export const postUpload = async (req, res) => {
const { title, description, hashtags } = req.body;
const video = new Video({
...
});
const dbVideo = await video.save();
return res.redirect("/");
};
데이터베이스에 데이터를 저장하는 또다른 간단한 방법이 있다.
export const postUpload = async (req, res) => {
const { title, description, hashtags } = req.body;
await Video.create({
...
});
return res.redirect("/");
};
이 방법은 시스템이 객체를 알아서 만들어주고 바로 데이터베이스에 저장할 수 있도록 해준다. 에러 발생을 잡기 위해서 try catch문을 사용해준다.
6.17 Exceptions and Validation
데이터타입은 상세하게 지정해줄수록 좋다. 꼭 필요한 데이터 값이 있다면 스키마 required를 설정해주는 것이 좋다.
required가 true로 설정된 데이터는 데이터가 빠졌을 경우 에러를 발생시킨다.
default를 지정해준 데이터타입은 데이터가 빠졌어도 default값으로 저장된다.
const videoSchema = new mongoose.Schema({
title: { type: String, required: true },
description: { type: String, required: true },
createdAt: { type: Date, required: true, default: Date.now() },
hashtags: [{ type: String }],
meta: {
views: { type: Number, required: true, default: 0 },
rating: { type: Number, required: true, default: 0 },
},
});
데이터 타입으로 인한 에러가 발생했을 경우 try-catch문으로 에러를 잡아줄 수 있고 해당 에러 메세지를 포함시켜 렌더링 시키면 사용자에게 에러메세지를 보여줄 수 있다.
export const postUpload = async (req, res) => {
const { title, description, hashtags } = req.body;
try {
await Video.create({
...
});
return res.redirect("/");
} catch (error) {
return res.render("upload", {
pageTitle: "Upload Video",
errorMessage: error._message,
});
}
};
6.18 More Schema
https://mongoosejs.com/docs/schematypes.html
Mongoose v6.7.2: SchemaTypes
SchemaTypes handle definition of path defaults, validation, getters, setters, field selection defaults for queries, and other general characteristics for Mongoose document properties. You can think of a Mongoose schema as the configuration object for a Mon
mongoosejs.com
몽구스에서 지원하는 스키마 옵션을 확인할 수 있다.
데이터의 길이를 제한하는 max/minlength 옵션을 사용할 경우에는 input 태그에도 해당 속성을 주어야 한다.
6.19 Video Detail
moogodb에서 자동으로 설정해주는 id값은 16진수로 이루어진 24자리의 string값이다
하지만 우리는 라우터에서 id가 숫자로 이루어져있을 경우에만 watch 컨트롤러를 불러오게 제한을 두었기 때문에 에러가 발생한다.
videoRouter.get("/:id(\\d+)", watch);
이를 해결하기 위해서 정규식을 사용한다.
videoRouter.get("/:id([0-9a-f]{24})", watch);
해당 정규식은 0-9 , a-f로 이루어진 24자리 string이라는 뜻이다.
이제 watch 컨트롤러에서 id를 통해 video를 불러와보자.
몽구스에서 지원하는 model 관련 함수인 findOne()과 findById()는 특정 객체를 찾는데 도움을 준다.
findOne()은 조회수 같은 모든 condition을 조건으로 보내 조건에 맞는 객체를 찾아준다.
findById()는 id로 객체를 찾아준다.
export const watch = async (req, res) => {
const { id } = req.params;
const video = await Video.findById(id);
res.render("watch", { pageTitle: video.title, video });
};
findById()를 사용하여 watch 컨트롤러를 수정해준다.
6.20 Edit Video part One
존재하지 않는 비디오 상세 페이지로 접근하는 것을 방지하기 위하여 watch 컨트롤러에서 video가 존재하지 않을 경우 404템플릿을 렌더링시킬 수 있게 한다.
이제 비디오 수정 기능을 구현해보자.
원하는 비디오를 가져오기 위하여 getEdit 컨트롤러를 수정한다 .
export const getEdit = async (req, res) => {
const { id } = req.params;
const video = await Video.findById(id);
if (video === null) {
return res.render("404", { pageTitle: "Video not found" });
}
res.render("edit", { pageTitle: `Editing: ${video.title} `, video });
};
id를 통해 수정하고 싶은 비디오를 가져오는 방법이 앞서 했던 방식과 동일하다.
6.21 Edit Video part Two / 6.22 Edit Video part Three
이제 수정한 내용을 다시 저장해보도록 하자.
export const postEdit = async (req, res) => {
const { id } = req.params;
const { title, description, hashtags } = req.body;
const video = await Video.findById(id);
if (video === null) {
return res.render("404", { pageTitle: "Video not found" });
}
video.title = title;
return res.redirect(`/videos/${id}`);
};
이렇게 직접 하나하나 값을 다시 저장시키는 방법도 있지만 더 간편한 방법이 있다.
몽구스에서 지원하는 함수인 findByIdAndUpdate()를 사용하여 간단하게 데이터베이스를 업데이트 시킬 수 있다.
export const postEdit = async (req, res) => {
const { id } = req.params;
const { title, description, hashtags } = req.body;
const video = await Video.exists({ _id: id });
if (video === null) {
return res.render("404", { pageTitle: "Video not found" });
}
await Video.findByIdAndUpdate(id, {
title,
description,
hashtags: hashtags
.split(",")
.map((word) => (word.startsWith("#") ? word : `#${word}`)),
});
return res.redirect(`/videos/${id}`);
};
6.23 Middlewares
몽구스에서 미들웨어를 만들어보자. 몽구스는 다양한 미들웨어를 지원한다.
미들웨어는 반드시 모델 생성 전에 만들어져야 하고 미들웨어를 선언할 때는 pre()를 사용한다.
videoSchema.pre("save", async function () {
this.hashtags = this.hashtags[0]
.split(",")
.map((word) => (word.startsWith("#") ? word : `#${word}`));
});
save 미들웨어에서 this는 저장되는 문서를 가리킨다. function()은 save되기 전에 실행되기 때문에 데이터베이스에 저장되기 전 데이터 처리를 할 수 있다.
6.24 Statics
findByIdAndUpdate() 에서는 save훅업이 발생하지 않기 때문에 앞서 설명한 미들웨어 적용이 불가능하다. 미들웨어 대신 직접 함수를 만들어 데이터를 변경시켜줄 수 있다.
//Video.js
export const formatHashtags = (hashtags) =>
hashtags.split(",").map((word) => (word.startsWith("#") ? word : `#${word}`));
//videoController.js
import Video, { formatHashtags } from "../models/Video";
hashtags: formatHashtags(hashtags)
몽구스에서 지원하는 모델 function()을 직접 만들어 줄 수 있는데 이걸 static이라고 한다. static은 모델에 자동으로 포함되는 함수기 때문에 import 할 필요도 없다.
//Video.js
videoSchema.static("formatHashtags", function (hashtags) {
return hashtags
.split(",")
.map((word) => (word.startsWith("#") ? word : `#${word}`));
});
//videoController.js
hashtags: Video.formatHashtags(hashtags)
6.25 Delete Video
특정 비디오를 데이터베이스에서 삭제하는 방법은 findByIdAndDelete() 함수를 사용하여 간단하게 할 수 있다.
//videoRouter.js
videoRouter.get("/:id([0-9a-f]{24})/delete", deleteViedeo);
//videoController.js
export const deleteViedeo = async (req, res) => {
const { id } = req.params;
await Video.findByIdAndDelete(id);
return res.redirect("/");
};
6.26 Search part One / 6.27 Search part Two
키워드를 통해 영상 검색하는 기능을 구현해보자.
export const search = async (req, res) => {
const { keyword } = req.query;
let videos = [];
if (keyword) {
videos = await Video.find({
title: {
$regex: new RegExp(`${keyword}$`, "i"),
},
});
}
return res.render("search", { pageTitle: "Search", videos });
req.query를 불러오면 url에 있는 모든 정보를 얻을 수 있다. get 방식으로 접근하여 keyword값이 url에 포함되어 있기 때문에 req.query를 통해 가져온다.
모델의 find() 함수를 사용하여 제목에 해당 키워드를 가진 영상을 찾을 수 있는데 이때 RegExp를 사용하면 더 다양하게 찾을 수 있다.
RegExp는 MongoDB가 지원하는 기능으로 패턴을 사용해 텍스트를 판별해준다.
'강의기록' 카테고리의 다른 글
[유튜브 클론코딩] #5 TEMPLATES (0) | 2022.05.17 |
---|---|
[유튜브 클론코딩] #4 ROUTERS (0) | 2022.05.17 |
[유튜브 클론코딩] #3 Introduction To EXPRESS (0) | 2022.05.17 |
[유튜브 클론코딩] #1 #2 Setup (0) | 2022.05.17 |