c- create
R- Read
U- Update
D- Delete
-> real DB를 이용해서 비디오를 생성하고, 보고, 업데이트하고, 삭제할 수 있도록 만들것임.
Schemas
몽구스의 모든 것은 스키마로 시작합니다.
각 스키마는 MongoDB 컬렉션에 매핑되고 해당 컬렉션 내 문서의 모양을 정의합니다.
스키마:물리적인 장치로부터 논리적인 데이터 베이스 레코드(data base record)를 매핑(mapping)하는 데 사용되는 정의 정보를 말한다. 즉 쿠키틀 이라고 보면 될거같네요..!
Schema 가이드
Models
mongoose.model(modelName, schema):
모델은 스키마 정의에서 컴파일된 멋진 생성자입니다. 모델의 인스턴스를 document라고 합니다. 모델은 기본 MongoDB 데이터베이스에서 문서를 만들고 읽습니다.
models1
models2
import mongoose from "mongoose";
const videoSchema = new mongoose.Schema({
title: String,//{type:String}이라 써도됨.
description: String,
createdAt: Date,
hashtags:[{type:String}],
meta: {// meta: 인지하는 것(스스로 인지하다) -> 뷰, rating은 인지하는 것임.
views:Number,
rating:Number,
},
})
const Video = mongoose.model("Video",videoSchema);//model(modelName, Schema)
export default Video;
import Video from "./models/Video";
-> 위의 연결로 우리의 db는 model을 인지하게 됨.
-> db를 mongoose와 연결시켜서 model을 인식시키는 것이란 사실이 젤 중요!
사용하기에 앞서, 코드 정리 & 파일 정리
1. /src파일에 init.js파일 하나 만들기 ㄲ, 그리고 server.js에서 다음의 코드들을 잘라서 붙여넣기 ㄱ ㄱ. 이 때, server.js에서 export default app;해주기
export default app;
import "./db";
import videoModel from "./models/Video";
import app from "./server";
const PORT = 4000;
const handleListening = () =>
console.log(`Server listening on port http://localhost:${PORT}`);
app.listen(PORT, handleListening);
이해 안되는 내용 이해본!
서버에 관련된 코드들이 server.js에 있다는것을 init.js에게 알려주지않아도 되나요?
서버를 init.js 로 열어서 헷갈리네요.
같은 파일에 있지않은 express관련 코드들은 자동적으로 server.js에 있다고 찾아지는건가요?
-> We imported server on init, so everything is being imported.
init.js에서는 server.js의 default 값인 app만 import해왔는데, server.js에서 express와 관련되어 import된 것들은 어찌 import되는지?
A): 우리가 default값으로 app을 import했다고 생각되겠지만, 크게 보면 우리는 server.js를 import한 것이다. 그 중 우리가 default값으로 따로 import된 게 app인거지, 결론적으로는 server.js를 import한 것이기 때문에 express와 관련되어
import된 값들도 불러온 것임. 즉, server.js라는 파일도 import한 것이고, fn인 app도 import한 것임. 두 개 다 import한 것이고, 이 중 app을 사용하기 위해 default값을 해준것이다.
export const home = (req,res) => {
return res.render("home",{pageTitle : "Home"});
}
export const watch = (req,res) => {
const id = req.params.id;
return res.render("watch",{pageTitle : `Watching`});
}
export const getEdit = (req,res) => {
const id = req.params.id;
return res.render("edit",{pageTitle:`Editing`});
}
export const postEdit = (req, res) => {
const {id} = req.params;
const {title} = req.body;
return res.redirect(`/videos/${id}`);
}
export const getUpload = (req,res) => {
return res.render("upload",{pageTitle:"Upload Video"});
}
export const postUpload = (req,res) => {
// here we will add a video to the videos array.
return res.redirect("/");
}
import Video from "../models/Video";
Video.find()//를 이용할것임
Video.find()에는 두가지 방법이 있음.
database는 JS외부의 영역이기 때문에 db가 바쁘거나 처리할 일이 많아지게 되면
불러오는데 시간이 꽤 걸릴 수 있다. 그래서 callback / promise를 사용하는것.
callback: 무엇인가 발생하고 나서 실행되는 함수. JS에서는 기다림을 표현하는
하나의 방법이다. find에 callback 사용시, configuration과 callback
함수가 필요.
-> configuration
Video.find({}, );//configuration의 {}가 비었다면 모든 형식을 찾는다는 걸 뜻함.-> 모든 형태의 비디오를 찾음.
-> callback
callback은 err과 docs라는 signature(변수?)를 가짐.
export const home = (req,res) => {
Video.find({}, (error,videos) => {
console.log("error",error);
console.log("videos",videos);
});
return res.render("home",{pageTitle : "Home",videos:[]});
};
//callback의 두 변수의 자리는 (error, document) 이 자리임 변수명으로는 뭘 써줘도 상관 X
mongoose는 Video.find({} 부분을 database(video.js의 videomodel)에서 불러올 것임. 그리고 database가 반응하면 그 뒤의 callback함수를 실행시킬 것임.
database가 javascript 밖에서 수행되다보니 find({}, ...)가 다른(console.log같은..)것 보다 늦게 수행된다.
그래서 callback은 코드의 맨 마지막에 실행됨. 이게 callback은 파워임.
여기서는 rendering을 한 뒤에, db를 찾고 callback함수가 실행됨.
Server listening on port http://localhost:4000
✅ Connected to DB
GET / 304 99.450 ms - -
error null
videos []
callback함수는 순서에 상관없이 젤 마지막에 출력됨.
우리는 이 callback의 힘을 이용할 것임.
다음과 같이 코드를 작성하면, db에서 video를 받아올 수 있고, db 검색이 끝나야 rendering이 시작되게 할 수 있음.
즉, 여기서는 db 검색이 끝나야 rendering이 됨.
export const home = (req,res) => {
Video.find({}, (error,videos) => {
if(error){
return res.render("server-error")
}
return res.render("home",{pageTitle : "Home",videos});//videos를 db에서 받아옴. db를 검색한 뒤, rendering이 됨.
});
};
promise를 쓰면 위의 callback에서 함수 내에 함수를 쓰는 걸 피할 수 있음.
await: 해당 코드가 끝날 때까지 다음 순서의 코드를 진행시키지 않음. 즉, 해당 코드를 기다려주는 역할을 함. 코드 규칙상, await는 function안에서만 사용가능한데, 해당 fn이 asynchronous(async) 일 때만 가능함. 그래서 변수에다가 async를 해주는 것임.
-> find()앞에 await를 쓰면 callback함수가 필요하지 않다는 걸 알게됨. -> 그래서 find는 찾
아낸 비디오를 바로 출력함. 즉, 위에서 callback을 이용하면 callback으로 인해 파일을 맨
마지막에 찾아냈지만, await를 이용하먄 위에서부터 순서대로 코드를 진행시킴.
즉, await은 db에서 파일을 찾을 때까지 기다려주고 차근차근 코드를 진행시킴!
즉, 코드마다 걸리는 시간이 달라서 출력되는 순서가 꼬이는 경우를 해결해줌!
export const home = async(req,res) => {
console.log("i start");
const videos = await Video.find({});
console.log("i finished");
console.log(videos);
return res.render("home",{pageTitle : "Home",videos});//videos를 db에서 받아옴.
};
이 때 에러는 어떻게 처리하느냐?
try/catch구문을 이용해 처리한다. -> try에서 에러가 나면 catch로 바로 넘어감
export const home = async(req,res) => {
try{
const videos = await Video.find({});
return res.render("home",{pageTitle : "Home",videos});
} catch{
return res.render("server-error");
}
};
1. upload.pug
extends base.pug
block content
form(method="POST") <!--action="/videos/upload"은 어차피 아무 url도 안 적어주면 해당 url에서 실행하기 때문에 없어도 된다.-->
input(name="title",placeholder="Title", required, type="text")
input(name="description",placeholder="Description", required, type="text")
input(name="hashtags",placeholder="Hashtags, separated by comma.", required, type="text")
input(type="submit", value="Upload Video")
input을 추가해준다. name과 placeholder까지 수정해준다.
2. videocontroller
export const postUpload = (req,res) => {
// here we will add a video to the videos array.
const { title, description, hashtags } = req.body;
// console.log(title, description, hashtags);
return res.redirect("/");
}
upload.pug에서 적은 title, description, hashtags를 받아온다. console창으로 잘 받았는지 확인도 해보기!
3. 우리가 받은 데이터를 document로 만들어(데이터를 가진 비디오라 생각하면 편함), database에 저장해줘야함.
export const postUpload = (req,res) => {
// here we will add a video to the videos array.
const { title, description, hashtags } = req.body;
const video = new Video({
title:title,
description:description,
createdAt: Date.now(),
hashtags: hashtags.split(",").map((word) => `#${word}`),
meta:{
views:0,
ratings:0,
},
});
// console.log(video);
return res.redirect("/");
document는 위에 처럼, Video(video.js에서 만든 model명으로)로 새로운 model을 만든 뒤, Schema에 따라 내용을 적어주면 된다.
hashtags만들기:
split: 위와 같이 (,)에 따라 나눠주며 적어준 String(ex: "home, me, you")을 (,)에 따라 나누어 array 형태로 보여준다.
map: array내의 모든 item에게 어떠한 특성을 적용시킬 때 사용.
console창 -> mongoose가 id를 부여해줌 ! 굉장히 신기함!
이렇듯, 새로운 비디오(object)가 만들어졌다. 이 object는 JS세계에는 존재하지만 아직 저장이 안 된 상태임.
export const postUpload = async(req,res) => {
// here we will add a video to the videos array.
const { title, description, hashtags } = req.body;
const video = new Video({
title:title,
description:description,
createdAt: Date.now(),
hashtags: hashtags.split(",").map((word) => `#${word}`),
meta:{
views:0,
ratings:0,
},
});
await video.save(); //const dbVideo = await video.save();
//console.log(dbVideo);
return res.redirect("/");
-1. object명.save()를 하면 database에 저장된다. video.save(); 를 하면된다.
-2. 이후에 aysnc, await를 해줘야한다. -> save(); 는 시간이 오래 걸리므로(db가 JS외부에 있어서)
다음 코드로 넘어가기 전에 기다려줘야 한다. 따라서 async와 await을 써야한다.
-3. 위의 처럼 하고 upload 하면 video가 database에 저장되고, home에서 이 video를 볼 수 있게됨.
export const postUpload = async(req,res) => {
// here we will add a video to the videos array.
const { title, description, hashtags } = req.body;
await Video.create({
title:title,
description:description,
createdAt: Date.now(),
hashtags: hashtags.split(",").map((word) => `#${word}`),
meta:{
views:0,
rating:0,
},
});
return res.redirect("/");
}
-> new Video를 만들고 db에 저장하는 것까지 해줌.
-> database의 모든 videos를 보고싶다면 home controller에서 console.log(videos)를 하면됨.
const videos = await Video.find({}); // <- 여기 밑에 콘솔
console.log(videos);
-----------------------------terminal로 db확인하기.----------------------
4. 발생할 수 있는 오류들 대비하기(try & catch 이용)
object내의 형식에 맞지 않는 데이터를 입력했을 경우, (String-> number or 그 반대) mongoose는 그 데이터를 빼고 new Video(object)를 형성할 것이다.
createdAt이 안 적힐 경우, required가 아니기 때문에 없는 채로 비디오가 생성됨. 이를 방지하고자 required해주기 ㄱ ㄱ.
그리고 매번 createdAt: Date.now()를 쓰기 귀찮으니 default값을 주자. 그런데 이 때 default의 Date.now에 ()를 안 써준 이유는 ()는 함수를 실행시키는 명령어기 때문에, ()를 하면 함수를 즉각 실행해서 ()를 적은 시간값이 나옴. 따라서 ()를 없애줌으로써 비디오가 만들어질 때의 시간이 적히도록함 -> mongoose가 알아서 처리해주기 때문!
video.js에서 ㄱ ㄱ
createdAt: {type:Date, required:true, default:Date.now},
export const postUpload = async(req,res) => {
// here we will add a video to the videos array.
const { title, description, hashtags } = req.body;
try{
await Video.create({
title:title,
description:description,
//createdAt: "lalala",
hashtags: hashtags.split(",").map((word) => `#${word}`),
meta:{
views:0,
rating:0,
},
});
return res.redirect("/");
}
catch(error){
return res.render("upload",{
pageTitle:"Upload Video",
errorMessage: error._message,
});
}
}
이후 upload.pug로 ㄱ ㄱ
block content 내에서 form 바로 위에 다음과 같은 코드 추가 ㄱ ㄱ
if errorMessage
span=errorMessage
-> 그렇게 되면, 만약 upload중 오류가 생기면 다시 upload창으로 render되면서 오류도 같이 뜰거임
5. Schema 데이터 구체화 작업
meta: {
views:{type:Number, default:0, required:true},
rating:{type:Number, default:0, required:true},
},
title: {type:String, required:true, trim:true},
description: {type:String, required:true, trim:true},
hashtags:[{type:String, trim:true}],
input(name="title",placeholder="Title", required, type="text", maxlength=80)
input(name="description",placeholder="Description", required, type="text", minlength=20)
video.js - Schematitle: {type:String, required:true, trim:true, maxlength:80},
description: {type:String, required:true, trim:true, minlength:10},
hr:구분선을 만들어줌
small: 덧붙임 글 요소
mixin video(video)
div
h4
a(href=`/videos/${video.id}`)=video.title
p=video.description
small=video.createdAt
hr
-> 우리가 videoRouter에서 video.id로 각각의 watch.pug로 가야되는데 이 때, watch에 대한 get을 할 때, id를 숫자 형태로 지정해줘서 get이 안됨. 그래서 이걸 get할 수 있도록 이해 가능한 형태로 바꿔줄것임.