[Youtube clone coding] #6 MongoDB and Mongoose (1)

 

#6.0, #6.1 Array Database

먼저 fake database를 만들어주자.

let videos = [
    {
        title: "Video #1",
        rating: 5,
        comments: 2,
        createAt: "2 minutes ago",
        views: 59,
        id: 1,
    },
    {
        title: "Video #2",
        rating: 4,
        comments: 100,
        createAt: "1 hours ago",
        views: 1000,
        id: 2,
    },
    {
        title: "Video #3",
        rating: 3,
        comments: 9,
        createAt: "40 minutes ago",
        views: 59,
        id: 3,
    }
];

export const trending = (req, res) => {return res.render("home", {pageTitle: "Home", videos})};
export const search = (req, res) => {return res.send("Search")};

export const edit = (req, res) => {return res.render("edit")};
export const watch = (req, res) => {return res.render("watch")};
export const upload = (req, res) => {return res.send("Upload Video.")};
export const deleteV = (req, res) => {return res.send("Delete Video.")};

 

videos라는 fake array database를 만들어줬다.

 

pug에서 attribute에는 #{} 방식으로 변수와 일반 텍스트를 함께 사용할 수 없다.

대신 “안에 ${}로 입력하거나 +로 연결해주는 방식을 사용해야 한다.

mixin video(info)
    div    
        h4
            a(href=`/videos/${info.id}`)=info.title
        ul 
            li #{info.rating}/5
            li #{info.comments} comments.
            li Posted #{info.createAt}.
            li #{info.views} Views.

 

이제 비디오 제목을 누르면 해당 페이지로 이동할 수 있도록 만들어주었다.

 

export const watch = (req, res) => {
    const { id } = req.params;
    const video = videos[id-1];
    return res.render("watch", {pageTitle: `Watching ${video.title}`, video})
};

videocontroller.js

video 객체에 다음과 같이 접근해주고,

extends base.pug
    
block content 
    h3 #{video.views} #{video.views === 1 ? "view" : "views"}

 

watch.pug를 다음과 같이 바꿔주었다. Inline 연산자를 통해서 복수를 처리해줄 수 있다.

 

상대경로 vs 절대경로

웹해킹 문제에서도 위 방법을 사용해봤었다…. 잘 기억하길.

 

extends base.pug
    
block content 
    h3 #{video.views} #{video.views === 1 ? "view" : "views"}
    a(href=`${video.id}/edit`) Edit video →

 

이렇게 상대경로로 정해주면

Edit video가 생기고,

클릭하면 상대경로로 이동할 수 있게 된다!

 

#6.2, #6.3 Edit video

extends base.pug
    
block content 
    h4 Change Title of Video
    form(action='')
        input(placeholder='Video title',value=`${video.title}`,required)
        input(value='Send',type='submit')

 

먼저 간단하게 다음과 같이 edit.pug를 수정해주었다.

적용된 모습.

이제는 실제로 저 Send 버튼을 눌렀을 때 backend로 어떻게 데이터가 전송되는지에 대해 알아보자.

 

form의 default 값은 GET이다. method를 바꿔주려면 method=”POST”와 같이 선언해줘야 한다.

extends base.pug
    
block content 
    h4 Change Title of Video
    form(method="POST")
        input(placeholder='Video title',value=`${video.title}`,required)
        input(value='Send',type='submit')

 

 

그리고 videoRouter.js에 새로운 post 라우터를 선언해주고,

import express from "express";
import { watch, upload, deleteV, getEdit, postEdit } from "../controllers/videocontroller";

const videoRouter = express.Router();

videoRouter.get("/:id(\d+)", watch);
videoRouter.get("/:id(\d+)/edit", getEdit);
videoRouter.post("/:id(\d+)/edit", postEdit);

export default videoRouter;

 

그에 맞는 함수를 videocontroller.js에도 선언해줘야 한다.

export const getEdit = (req, res) => {
    const { id } = req.params;
    const video = videos[id-1];
    return res.render("edit", {pageTitle: `Editing ${video.title}`, video})
};

export const postEdit = (req, res) => {

};

 

 

이제 post 데이터를 어떻게 처리할 것인지에 대해 알아보자.

 

videoRouter.route("/:id(\d+)/edit").get(getEdit).post(postEdit);
// same as
videoRouter.get("/:id(\d+)/edit", getEdit);
videoRouter.post("/:id(\d+)/edit", postEdit);

 

먼저, 위와 같이 route를 활용해서 두줄을 한줄로 줄여줄 수 있다.

하나의 url에 get, post를 모두 사용할 때 쓸 수 있는 유용한 방법이다.

 

 

pug의 form 태그에서 데이터를 받아오기 위해서는 server.js에 express 미들웨어를 삽입해줘야 한다.

이렇게 express.urlencoded를 추가해주면 urlencoded 페이로드로 들어오는 요청을 구문 분석하고 바디 파서 기능을 수행해준다.

 

export const postEdit = (req, res) => {
    const { id } = req.params;
    const {title} = req.body;
    videos[id-1].title = title;
    return res.redirect(`/videos/${id}`);
};

 

그리고 videocontroller.js의 postEdit 함수를 다음과 같이 바꿔주면…

title이 바뀌게 된다!!!!

 

+req.body에서 데이터를 보기 위해서는 꼭 name을 넣어주자!

 

#6.5, #6.6 More Practice

실제 데이터베이스를 다루는 부분으로 넘어가기 전에, 지금까지 배운 것을 더 연습해보자.

이번엔 비디오를 업로드하는 페이지를 만들어볼 것이다.

  1. videoRouter.js에 추가해주기
videoRouter.route("/upload").get(getUpload).post(postUpload);

videoRouter.js

  1. videocontroller.js에 함수 추가해주기
export const getUpload = (req, res) => {
    return res.render("upload",{pageTitle: 'Upload Video'})
};

export const postUpload = (req, res) => {
    return res.redirect('/');
};

videocontroller.js

  1. upload.pug 만들어주기
extends base.pug

block content 
    form(method='POST')
        input(placeholder='title',type='text',required)
        input(type='submit',value='Upload')

 

 

  1. postUpload 수정
export const postUpload = (req, res) => {
    const {title} = req.body;
    const newVideo = {
        title,
        rating: 0,
        comments: 0,
        createAt: "just now",
        views: 0,
        id: videos.length + 1,
    }
    videos.push(newVideo);
    return res.redirect('/');
};

 

우리의 가짜 Database인 array에 newVideo 객체를 넣어주는 코드를 추가해주었다.

 

upload에서 ‘Last Video’라는 제목을 가진 video를 upload 해주면,

새로운 video가 추가된 것을 확인할 수 있다.

title을 클릭하면,

id가 4인 video 페이지로 이동된다!

 

 

#6.7, #6.8 MongoDB & Mongoose

MongoDB는 noSQL 기반 DB이다.

즉, SQL로 쿼리를 주고받지 않고, document-based이므로 자바스크립트 객체와 같은 모습으로 데이터를 주고 받게 된다.

 

설치 방법

  1. brew tap mongodb/brew
  2. brew install mongodb-community@7.0
  3. mongosh → mongoDB shell 열기

 

Mongoose → MongoDB와 node.js를 연결해주는 역할!

  1. npm i mongoose

 

import mongoose from "mongoose";

mongoose.connect("mongodb://127.0.0.1:27017/wetube");

const db = mongoose.connection;

const handleOpen = () => console.log("Connected to DB ✅");
const handleError = (error) => console.log(`DB ERROR : ${error}`);

db.on("error", handleError);
db.once("open", handleOpen);

db.js

mongoose.connect()의 url은 mongosh를 입력해서 얻은 url이다.

db.on → 이벤트가 발생할때마다 항상 실행되는 함수

db.once → 처음 한번만 실행되는 함수

 

해당 ./db를 server.js에 import 해주어야 한다.

 

#6.9, #6.10 CRUD & Video model

CRUD→C: Create, R: Read, U: Update, D: Delete

/src에 models 디렉토리를 만들고, /models에 Video.js를 만든다.

 

이제 Video model을 만들 것이다. DB 스키마를 만드는 것이라고 생각하면 될 듯!

 

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;

Video.js

위와 같이 videoSchema를 만들어주었다.

그리고 Video라는 모델을 만들어서 export 해주었다.

마지막으로 server.js에 /models/Video.js를 import 해주면 된다.

import “./models/Video.js”

 

#6.11, #6.12, #6.13 Our First Query

우리는 server.js에 모델을 import 했다. 그런데 import한 모듈이 많아질수록 코드가 길어질 것이다.

따라서 init.js를 따로 만들어주자.

import "./db";
import "./models/Video";
import app from "./server";

const PORT = 4000;

const handleListening = () => 
    console.log(`✅ Server listening on port http://localhost:${PORT} 🚀`);

app.listen(PORT, handleListening);

init.js

import express from "express";
import morgan from "morgan";
import globalRouter from "./routers/globalRouter";
import videoRouter from "./routers/videoRouter";
import userRouter from "./routers/userRouter";

const app = express();
const logger = morgan("dev");

app.set("view engine", "pug");
app.set("views", process.cwd() + "/src/views");
app.use(logger);
app.use(express.urlencoded({ extended: true }));
app.use("/",globalRouter);
app.use("/videos",videoRouter);
app.use("/users",userRouter);

export default app;

server.js

init.js와 server.js로 분리해주었다.

 

그리고 package.json에서 scripts를 변경해줘야 한다.

"scripts": {
    "dev": "nodemon --exec babel-node src/init.js"
  },

package.json

 


 

이제 가짜 array database를 삭제하고, 실제 database와 연결시켜보자!

db로부터 데이터를 가져온다는 것은 즉, js 코드 내에 없는 데이터를 받아와서 사용한다는 것이다. 즉, db로부터 받아온 에 실행을 해야한다는 것이며, 만약 데이터를 받아오기 전에 실행되면 문제가 발생할 수 있다!!!!

 

쿼리를 주는 방법으로는 callback과 promise가 존재한다.

  • callback

→ 더 이상 사용하지 않는다고 하며 에러를 출력한다..

 

  • promise
export const home = async(req, res) => {
    try{    
        const videos = await Video.find({});
        console.log(videos);
        return res.render("home", {pageTitle: "Home", videos});
    }
    catch(err){
        return res.render("server-error",err);
    }

};

 

async, await를 사용하면, await 부분에서 데이터를 받아올 때까지 기다릴 수 있다.

만약 await 시에 에러가 발생하면 catch 부분으로 넘어간다. (오류 처리 가능)

 


 

** 본 글은 노마드 코더의 ‘유튜브 클론코딩’ 강의를 참조하여 작성하였습니다. **

 

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤