# 6.9- 6.18 CRUD & Video model

이원규·2022년 6월 19일
0

Itube

목록 보기
20/46
post-thumbnail

CRUD project

c- create
R- Read
U- Update
D- Delete

-> real DB를 이용해서 비디오를 생성하고, 보고, 업데이트하고, 삭제할 수 있도록 만들것임.

setup CRUD pjt

  1. src/파일에 models라는 파일 만듦.
  2. 그 파일 안에 Video.js파일을 만듦.
    -> 비디오 모델을 만드는 것임.
    -> 그럼 모델은 뭘까?
    mongoose는 우리가 mongoDB와 커뮤니케이션 하도록 도와줌. 이 때 mongoose가 커뮤 니케이션을 잘하려면 우리가 조금은 도움을 줘야함.
    즉, 우리가 mongoose에게 우리 애플리케이션 데이터들이 어떻게 생겼는지 알려줘야함.
    예를 들어, 비디오 제목이 있고 세부설명 등이 있고 등등 알려줘야함. 그 데이터가
    어떻게 생겼느지 알려줘야함. 이렇게 알려주면 mongoose는 우리가
    만들고, 수정하고, 삭제하고, 검색하는 걸 도와줄 것임.
    타이틀은 가지고 있나? views는 숫자인가 스트링인가? 등
    이런걸 알려주기 위해 우리가 model을 만드는 것임. 한 마디로 여러 복제본을 만들기
    위해 틀을 만드는 것임.

6.10 Video model

Schema & model

Schemas
몽구스의 모든 것은 스키마로 시작합니다.
각 스키마는 MongoDB 컬렉션에 매핑되고 해당 컬렉션 내 문서의 모양을 정의합니다.
스키마:물리적인 장치로부터 논리적인 데이터 베이스 레코드(data base record)를 매핑(mapping)하는 데 사용되는 정의 정보를 말한다. 즉 쿠키틀 이라고 보면 될거같네요..!
Schema 가이드

Models
mongoose.model(modelName, schema):
모델은 스키마 정의에서 컴파일된 멋진 생성자입니다. 모델의 인스턴스를 document라고 합니다. 모델은 기본 MongoDB 데이터베이스에서 문서를 만들고 읽습니다.
models1
models2

Schema & model 만들기 in models/Video.js

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;

어디서든 model을 사용할 수 있도록 server.js에 import해주기

  1. model을 연결하기 전, database가 연결되었는지 확인. 연결됐으면 2번으로 ㄱ
  2. 다음 코드를 작성하여 model을 import해주기
import Video from "./models/Video";

-> 위의 연결로 우리의 db는 model을 인지하게 됨.
-> db를 mongoose와 연결시켜서 model을 인식시키는 것이란 사실이 젤 중요!

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값을 해준것이다.

  1. video controller의 가짜 db 지우기
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("/");
}
  1. trending -> home으로 바꿔서 globalrouter 에서도 trending -> home으로 바꿔주기

사용하기

  1. videocontroller에서 먼저 사용할 것임.
    먼저 import해주셈
import Video from "../models/Video";
  1. model query 사용법
Video.find()//를 이용할것임

Video.find()에는 두가지 방법이 있음.

database는 JS외부의 영역이기 때문에 db가 바쁘거나 처리할 일이 많아지게 되면
불러오는데 시간이 꽤 걸릴 수 있다. 그래서 callback / promise를 사용하는것.

-># callback을 이용.

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를 이용. <- 더 많이 이용할 것임.

promise를 쓰면 위의 callback에서 함수 내에 함수를 쓰는 걸 피할 수 있음.

await & async

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. return의 역할 : 본질적인 return의 역할보다는 function을 마무리짓는 역할로 사용되고 있음.
  • 이러한 경우 return이 없어도 정상적으로 동작하지만 실수를 방지하기 위해 return을 사용
  1. render한 것은 다시 render할 수 없음
  • redirect(), sendStatus(), end() 등등 포함 (express에서 오류 발생)

upload 만들기

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에 저장해줘야함.

1번 방법 -> new, save

-> document로 만들기. videocontroller에서

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세계에는 존재하지만 아직 저장이 안 된 상태임.

-> document를 database에 전송 & 저장하기.

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를 볼 수 있게됨.

2번 방법 -> create

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},
  • createdAt이 date형식이 아닌 경우, mongoDB는 video만드는 걸 거부함 -> try,catch로
    잡은 뒤, 에러 메시지를 upload.pug로 보내줘서 이걸 보여주도록 하면 됨.
    catch는 error를 인수로 가질 수 있다.
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 데이터 구체화 작업

Schema 데이터 구체화 요소들

  • meta코드도 별로 안 이뻐서 default값 처리 ㄱ ㄱ in video.js
meta: {
        views:{type:Number, default:0, required:true},
        rating:{type:Number, default:0, required:true},
    },
  • title, description required, trim처리(trim은 hashtags도)
	title: {type:String, required:true, trim:true},
    description: {type:String, required:true, trim:true},
    hashtags:[{type:String, trim:true}],
  • title, description 에 min,maxlength 부여 -> Schema & upload.pug 둘 다 부여해줘야함(해킹했을 땔 대비해서)
    upload.pug
    input(name="title",placeholder="Title", required, type="text", maxlength=80)
    input(name="description",placeholder="Description", required, type="text", minlength=20)
    video.js - Schema
    title: {type:String, required:true, trim:true, maxlength:80},
    description: {type:String, required:true, trim:true, minlength:10},

Mixin 수정하기 - video.pug

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할 수 있도록 이해 가능한 형태로 바꿔줄것임.

profile
github: https://github.com/WKlee0607

0개의 댓글