[TIL] 211126

Lee SyongΒ·2021λ…„ 11μ›” 26일
0

TIL

λͺ©λ‘ 보기
100/204
post-thumbnail

πŸ“ 였늘 ν•œ 것

  1. mongoose둜 CRUD operation κ΅¬ν˜„ν•˜κΈ° - search video / upload video

  2. schema / model / mongoose queries / mongoose validation


πŸ“š 배운 것

1. CRUD

CRUDλž€ 생성(Create), 읽기(Read), μˆ˜μ •(Update), μ‚­μ œ(Delete)λ₯Ό λ§ν•œλ‹€.
μ§€κΈˆλΆ€ν„° video(데이터)λ₯Ό CRUD ν•˜κΈ° μœ„ν•΄ video λͺ¨λΈμ„ λ§Œλ“€κ³ μž ν•œλ‹€.

1) λͺ¨λΈ(Model)

Mongoose둜 데이터 CRUDλ₯Ό κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„œλŠ” Mongoose에 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 데이터듀이 μ–΄λ–»κ²Œ μƒκ²ΌλŠ”μ§€(λͺ¨λΈ) μ•Œλ €μ€˜μ•Ό ν•œλ‹€.

ex) 'λΉ„λ””μ˜€μ—λŠ” 제λͺ©μ΄ μžˆλŠ”λ° κ·Έ 제λͺ©μ€ λ¬Έμžμ—΄μ΄κ³ , μ‘°νšŒμˆ˜κ°€ μžˆλŠ”λ° 그건 μˆ«μžμ•Ό.'

(1) μŠ€ν‚€λ§ˆ 및 λͺ¨λΈ μƒμ„±ν•˜κΈ°

  • src 폴더 μ•ˆμ— models 폴더λ₯Ό λ§Œλ“  ν›„ κ·Έ μ•ˆμ— Video.js νŒŒμΌμ„ λ§Œλ“ λ‹€.
    Video.js νŒŒμΌμ—μ„œ mongooseλ₯Ό import ν•œ ν›„ videoSchemaλ₯Ό λ§Œλ“ λ‹€.

πŸ’‘ Schema(μŠ€ν‚€λ§ˆ)

λͺ¨λΈμ„ μƒμ„±ν•˜κΈ° 전에 λ°μ΄ν„°μ˜ ν˜•μ‹κ³Ό ν˜•νƒœλ₯Ό μ •μ˜ν•΄λ†“μ€ 것을 λ§ν•œλ‹€.
mongoose.Schema({}) μ•ˆμ— μ •μ˜ν•΄μ€€λ‹€.

// Video.js
import mongoose from "mongoose";

const videoSchema = new mongoose.Schema({
  title: String, // String === { type: String }
  description: String,
  createdAt: Date,
  hashtags: [{ type: String }],
});
  • videoSchemaλ₯Ό μ΄μš©ν•΄ Video λͺ¨λΈμ„ μƒμ„±ν•œ ν›„ 이λ₯Ό export default ν•œλ‹€.

πŸ’‘ Model(λͺ¨λΈ)

μŠ€ν‚€λ§ˆλ₯Ό 톡해 λ§Œλ“œλŠ” μΈμŠ€ν„΄μŠ€λ₯Ό λ§ν•œλ‹€.
이λ₯Ό μ΄μš©ν•΄ μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€μ— CRUD μž‘μ—…μ„ ν•  수 μžˆλ‹€.
mongoose.model("λͺ¨λΈ 이름", μŠ€ν‚€λ§ˆ 이름); 을 톡해 λ§Œλ“ λ‹€.

// Video.js
const Video = mongoose.model("Video", videoSchema);
export default Video;
  • Video λͺ¨λΈμ„ λͺ¨λ‘κ°€ μ‚¬μš©ν•  수 μžˆλ„λ‘ Video.js νŒŒμΌμ„ import ν•΄μ•Ό ν•œλ‹€.
    server.js νŒŒμΌμ—μ„œ db.js νŒŒμΌμ„ import ν•˜λŠ” μ½”λ“œ λ°”λ‘œ 뒀에 Video.js νŒŒμΌμ„ import ν•˜λŠ” μ½”λ“œλ₯Ό μž‘μ„±ν•  μˆ˜λ„ μžˆλ‹€.
    κ·ΈλŸ¬λ‚˜, import μ½”λ“œκ°€ 점점 λ§Žμ•„μ§ˆ κ²ƒμ΄λ―€λ‘œ 이λ₯Ό λΆ„λ¦¬ν•΄μ„œ ν•œκΊΌλ²ˆμ— λͺ¨μ•„λ‘˜ νŒŒμΌμ„ λ”°λ‘œ λ§Œλ“œλŠ” 게 μ’‹λ‹€.

πŸ”Ž server.js와 init.js 뢄리

β‘  src 폴더 μ•ˆμ— λͺ¨λ“  것을 μ΄ˆκΈ°ν™”ν•΄μ£ΌλŠ” init.js νŒŒμΌμ„ λ§Œλ“ λ‹€.
init.js 파일이 λ°μ΄ν„°λ² μ΄μŠ€μ™€ λͺ¨λΈλ“€μ„ import ν•œ ν›„ μ΅œμ’…μ μœΌλ‘œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜(app)을 μ‹€ν–‰ν•˜λ„λ‘ server.js νŒŒμΌλ‘œλΆ€ν„° κ΄€λ ¨ μ½”λ“œλ“€μ„ κ°€μ Έμ˜¨λ‹€.
ν•œνŽΈ, init.js νŒŒμΌμ—λŠ” app에 λŒ€ν•œ 정보가 μ—†κΈ° λ•Œλ¬Έμ— server.js νŒŒμΌμ—μ„œ app을 λ§Œλ“€κ³  export default ν•œ ν›„, 이λ₯Ό init.js νŒŒμΌμ—μ„œ import ν•΄μ€˜μ•Ό ν•œλ‹€.

// server.js β†’ express와 app을 κ΅¬μ„±ν•œλ‹€
import express from "express";
import morgan from "morgan";
import globalRouter from "./routers/globalRouter";
import userRouter from "./routers/userRouter";
import videoRouter from "./routers/videoRouter";

const PORT = 4000;

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("/users", userRouter);
app.use("/videos", videoRouter);

export default app; // μΆ”κ°€ ❗
// init.js β†’ database와 modelsλ₯Ό import ν•œ ν›„, 이상이 μ—†λ‹€λ©΄ app을 μ‹€ν–‰μ‹œν‚¨λ‹€
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);

β‘‘ 이제 server.js νŒŒμΌμ€ app을 κ΅¬μ„±λ§Œ ν•  뿐 μ‹€ν–‰ν•  순 μ—†μ–΄μ„œ κΈ°μ‘΄ μ½”λ“œλ‘œλŠ” nodemon이 μ‹€ν–‰λ˜μ§€ μ•Šμ„ 것이닀.
문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ package.json νŒŒμΌμ—μ„œ dev script의 src/server.jsλ₯Ό src/init.js둜 μˆ˜μ •ν•œλ‹€.

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

2) λͺ¨λΈ μ‚¬μš©ν•˜κΈ° - search video

  • λ¨Όμ €, videoController.js νŒŒμΌμ—μ„œ 미리 λ§Œλ“€μ–΄λ‘” κ°€μ§œ λ°μ΄ν„°λ² μ΄μŠ€ κ΄€λ ¨ μ½”λ“œλ“€μ„ μ „λΆ€ μ‚­μ œν–ˆλ‹€.

  • video λͺ¨λΈμ„ μ‚¬μš©ν•˜κΈ° μœ„ν•΄ videoController.js에 video λͺ¨λΈμ„ import ν•œλ‹€.

// videoController.js
import Video from "../models/Video";
  • home 컨트둀러(trending 컨트둀러의 이름을 μˆ˜μ •ν•¨)μ—μ„œ Mongoose Queriesλ₯Ό μ΄μš©ν•΄ video λͺ¨λΈμ„ μ‚¬μš©ν•  수 μžˆλ‹€.

πŸ’‘ Mongoose Queries

Mongoose Queries μ°Έκ³ 

콜백 ν•¨μˆ˜ ν˜Ήμ€ Promiseλ₯Ό μ΄μš©ν•΄ mongoose query objectλ₯Ό λ°˜ν™˜ν•œλ‹€.

μ—¬κΈ°μ„œλŠ” Model.find()λΌλŠ” Mongoose Queryλ₯Ό 예둜 μ„€λͺ…해보겠닀.

πŸ”Ž 콜백 ν•¨μˆ˜ 이용

Model.find({}, 콜백 ν•¨μˆ˜);

{}λž€ search terms(검색 쑰건)λ₯Ό λ§ν•œλ‹€. λΉ„μ–΄ 있으면 λͺ¨λ“  ν˜•μ‹μ„ μ°Ύκ³  μžˆμŒμ„ λœ»ν•œλ‹€.
콜백 ν•¨μˆ˜λŠ” error와 docsλ₯Ό 가진닀. 이 κ²½μš°μ— docs λŒ€μ‹ μ— videos라고 μ“Έ 수 μžˆλ‹€.

Video.find({}, (error, videos) => {} );

mongooseκ°€ λͺ¨λ“  ν˜•μ‹μ˜ Videoλ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 뢈러였고 databaseκ°€ λ°˜μ‘ν•˜λ©΄, mongooseλŠ” 콜백 ν•¨μˆ˜λ₯Ό μ‹€ν–‰μ‹œν‚¨λ‹€.
μ½”λ“œλ₯Ό μ™„μ„±ν•˜λ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

// videoController.js
import Video from "../models/Video";

const handleSearch = (error, videos) => {
  console.log("error", error);
  console.log("videos", videos);
}

export const home = (req, res) => {
  Video.find({}, handleSearch);
  return res.render("home", { pageTitle: "Home", videos: [] });
}

이제 localhost:4000에 λ“€μ–΄κ°€λ©΄, console μ°½μ—λŠ” λ‹€μŒκ³Ό 같은 λ‚΄μš©μ΄ 좜λ ₯λœλ‹€.
λ°μ΄ν„°λ² μ΄μŠ€κ°€ videoλ₯Ό 찾을 λ•Œ μ—λŸ¬λŠ” μ—†μ—ˆκ³  video(즉, 데이터)도 μ—†μ—ˆλ‹€λŠ” λœ»μ΄λ‹€.
아직 데이터가 μ—†μ–΄μ„œ κ°€μ Έμ˜¨ 건 μ—†μ§€λ§Œ, μ–΄μ¨Œλ“  μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œλ₯Ό μ΄μš©ν•΄ λ°μ΄ν„°λ² μ΄μŠ€μ™€ 톡신에 μ„±κ³΅ν•œ 것이닀.

error null
videos []

μ—¬κΈ°μ„œ 잠깐 !
콜백 ν•¨μˆ˜μ— λŒ€ν•΄ μ•Œμ•„λ³΄κΈ° μœ„ν•΄ operation(Video.find()) λ‹€μŒμ— console.log()λ₯Ό μΆ”κ°€ν•΄λ΄€λ‹€.

export const home = (req, res) => {
  Video.find({}, handleSearch);
  console.log("Hello"); // μΆ”κ°€ ❗
  return res.render("home", { pageTitle: "Home", videos: [] });
};

그런데 이처럼 μ½”λ“œλŠ” [ operation, Hello, res.render ] 순으둜 μž‘μ„±ν–ˆλŠ”λ°
console μ°½μ—λŠ” [ Hello, res.render, operation ] 순으둜 찍힌 것을 확인할 수 μžˆλ‹€.

Server listening on port http://localhost:4000 🎈
β­• Connected to DB
Hello
GET / 304 79.991 ms - -
error null
videos []

즉, μ£Όμ†Œ 창에 localhost:4000을 μž…λ ₯ν•΄ λΈŒλΌμš°μ €κ°€ home νŽ˜μ΄μ§€λ₯Ό request ν•˜λ©΄ console.log("Hello") κ°€ μ‹€ν–‰λœλ‹€.
requestκ°€ μ™„μ„±λ˜μ–΄ responseλ₯Ό λ°›μœΌλ©΄(즉, home ν…œν”Œλ¦Ώμ΄ render 되면) Morgan middleware에 μ˜ν•΄ loggerκ°€ 좜λ ₯λœλ‹€.
κ·Έ ν›„ λ°μ΄ν„°λ² μ΄μŠ€μ™€ ν†΅μ‹ ν•œ 결과인 error와 videosκ°€ 좜λ ₯λœλ‹€.

μ΄λŠ” 데이터λ₯Ό 전솑받을 λ•ŒκΉŒμ§€ μ‹œκ°„μ΄ 걸리기 λ•Œλ¬Έμ΄λ‹€. handleSearch 콜백 ν•¨μˆ˜λŠ” 데이터가 μ™„μ „νžˆ μ „μ†‘λ˜λ©΄ κ·Έμ œμ„œμ•Ό μ‹€ν–‰λœλ‹€.

이 점을 μ‘μš©ν•΄ home 컨트둀러λ₯Ό λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•  수 μžˆλ‹€.
데이터λ₯Ό κ°€μ Έμ˜€λŠ” 데 μ„±κ³΅ν•œ κ²½μš°μ™€ μ—λŸ¬κ°€ 뜬 경우λ₯Ό λͺ¨λ‘ κ³ λ €ν•΄ μ½”λ“œλ₯Ό μž‘μ„±ν•œλ‹€.

// videoController.js (콜백 ν•¨μˆ˜ 이용 - μ΅œμ’…)
import Video from "../models/Video";

export const home = (req, res) => {
  Video.find({}, (error, videos) => {
    if(error) {
      return res.render("server-error");
    }
    return res.render("home", { pageTitle: "Home", videos });
  });
};

이제 home.pug 파일 μ•ˆμ˜ λ³€μˆ˜ videosμ—λŠ”, λ°μ΄ν„°λ² μ΄μŠ€λ‘œλΆ€ν„° 전솑받은 videosκ°€ ν• λ‹Ήλœλ‹€.
λΈŒλΌμš°μ €λŠ” ν•΄λ‹Ή μž‘μ—…μ΄ 끝날 λ•ŒκΉŒμ§€ κΈ°λ‹€λ €μ€€λ‹€.
즉, λ°μ΄ν„°λ² μ΄μŠ€ searchκ°€ λλ‚˜μ•Όλ§Œ renderκ°€ μ‹œμž‘λœλ‹€.

확인을 μœ„ν•΄ μ•„λž˜μ²˜λŸΌ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ©΄
μ½”λ“œλ₯Ό μž‘μ„±ν•œ μˆœμ„œλŒ€λ‘œ 즉, [ search start, search finished, logger ] 순으둜 좜λ ₯λ˜λŠ” 것을 확인할 수 μžˆλ‹€.
단, μ½”λ“œμ— 따라 μ‹€ν–‰ 속도에 차이가 μžˆμ„ μˆ˜λŠ” μžˆλ‹€.

// videoController.js (콜백 ν•¨μˆ˜ 이용 - ν™•μΈμš©)
export const home = (req, res) => {
  console.log("search start"); // 확인을 μœ„ν•΄ μΆ”κ°€
  Video.find({}, (error, videos) => {
    console.log("search finished"); // 확인을 μœ„ν•΄ μΆ”κ°€
    return res.render("home", { pageTitle: "Home", videos });
  });
};

πŸ”Ž promise 이용

κ·ΈλŸ¬λ‚˜, video λͺ¨λΈμ„ μ‚¬μš©ν•  λ•Œ 콜백 ν•¨μˆ˜λ₯Ό μ΄μš©ν•˜λ©΄, ν•¨μˆ˜ μ•ˆμ— 또 λ‹€λ₯Έ ν•¨μˆ˜λ₯Ό λ„£μ–΄μ•Ό ν•΄μ„œ λ²ˆκ±°λ‘­λ‹€.
이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ 콜백 ν•¨μˆ˜ λŒ€μ‹  promiseλ₯Ό μ΄μš©ν•  수 μžˆλ‹€.

// videoController.js (promise 이용 - ν™•μΈμš©)
export const home = async (req, res) => {
  console.log("search start");
  const videos = await Video.find({});
  console.log("search finished");
  console.log(videos);
  return res.render("home", { pageTitle: "Home", videos });
}
Server listening on port http://localhost:4000 🎈
β­• Connected to DB
search start
search finished
[]
GET / 304 87.183 ms - -

μ½”λ“œλ₯Ό μž‘μ„±ν•œ μˆœμ„œλŒ€λ‘œ 즉, [ search start, search finished, videos, logger ] 순으둜 좜λ ₯λ˜λŠ” 것을 확인할 수 μžˆλ‹€.
Video.find() μ•žμ— await을 적어주면, find()λŠ” 인수둜 콜백 ν•¨μˆ˜κ°€ λ“€μ–΄μ˜€μ§€ μ•ŠμœΌλ¦¬λž€ κ±Έ μ•Œκ³  μ°Ύμ•„λ‚Έ videoλ₯Ό λ°”λ‘œ 좜λ ₯ν•΄μ€€λ‹€.

await은 λ°μ΄ν„°λ² μ΄μŠ€λ‘œλΆ€ν„° κ²°κ³Ό 값을 받을 λ•ŒκΉŒμ§€ κΈ°λ‹€λ €μ€€λ‹€.

ν•œνŽΈ, 콜백 ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ μΆ”κ°€ μ½”λ“œ 없이 λ°”λ‘œ μ—λŸ¬λ₯Ό 가져와 좜λ ₯ν•  수 μžˆμ§€λ§Œ, promiseλ₯Ό μ‚¬μš©ν•˜λ©΄ μ—λŸ¬λ₯Ό 닀루기 μœ„ν•΄ try catch 문을 μ΄μš©ν•΄μ•Ό ν•œλ‹€.
β€» catch(error) { }λ₯Ό 톡해 error λ©”μ‹œμ§€λ₯Ό κ°€μ Έμ˜¬ μˆ˜λ„ μžˆμ§€λ§Œ μ—¬κΈ°μ„œλŠ” κ°€μ Έμ˜€μ§€ μ•Šμ•˜λ‹€.

// videoController.js (promise 이용 - μ΅œμ’…)
import Video from "../models/Video";

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");
  }
};

πŸ“Œ promiseλŠ” 콜백 ν•¨μˆ˜μ— λΉ„ν•΄ 훨씬 μ§κ΄€μ μ΄λΌλŠ” μž₯점이 μžˆλ‹€.
πŸ“Œ 콜백 ν•¨μˆ˜λ„ 싀행은 κΈ°λ‹€λ Έλ‹€κ°€ λ˜μ§€λ§Œ, μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” 이 쀄 μ € 쀄을 μ™”λ‹€κ°”λ‹€ν•˜λ©΄μ„œ μ½”λ“œλ₯Ό μ‹€ν–‰ν•œλ‹€.
πŸ“Œ 반면, promiseλ₯Ό μ‚¬μš©ν•˜λ©΄ await이 μžˆλŠ” λΆ€λΆ„μ—μ„œ μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” 계속 κΈ°λ‹€λ Έλ‹€κ°€ 기닀림이 λλ‚˜λ©΄ μˆœμ„œλŒ€λ‘œ μ½”λ“œλ₯Ό μ‹€ν–‰ν•œλ‹€.

μ—¬κΈ°μ„œ 잠깐 !
returnκ³Ό res.render에 λŒ€ν•˜μ—¬

export const home = (req, res) => {
  Video.find({}, (error, videos) => {
    return res.render("home", { pageTitle: "Home", videos });
  });
};

res.render() μ•žμ˜ return은, ν•¨μˆ˜ μ•ˆμ˜ 또 λ‹€λ₯Έ ν•¨μˆ˜ μ•ˆμ— μœ„μΉ˜ν•΄ 있기 λ•Œλ¬Έμ— μ•„λ¬΄λŸ° κΈ°λŠ₯도 ν•˜μ§€ μ•ŠλŠ”λ‹€.
κ·ΈλŸΌμ—λ„ res.render() μ•žμ— return을 μ¨μ£ΌλŠ” μ΄μœ λŠ”, res.render() ν•¨μˆ˜κ°€ μ‹€ν–‰λœ 후에 Video.find()의 콜백 ν•¨μˆ˜κ°€ μ’…λ£Œλ˜λ„λ‘ ν•˜κΈ° μœ„ν•¨μ΄λ‹€.

return이 μ€‘μš”ν•œ 게 μ•„λ‹ˆλΌ μ‘λ‹΅ν•˜κΈ° μœ„ν•΄ μ‹€ν–‰λ˜λŠ” ν•¨μˆ˜κ°€ μ€‘μš”ν•œ 것이닀.
μ‘λ‹΅ν•œ 후에 또 λ‹€μ‹œ 응닡할 수 μ—†λ‹€.

예λ₯Ό λ“€μ–΄, res.render() 이후에 res.end() 등을 ν•  수 μ—†λ‹€.
μ•„λž˜μ™€ 같이 μ„œλ²„κ°€ μ‘λ‹΅ν•˜λŠ” μ½”λ“œλ₯Ό ν•˜λ‚˜ 더 μΆ”κ°€ν•΄λ³΄μž.

export const home = (req, res) => {
  Video.find({}, (error, videos) => {
    return res.render("home", { pageTitle: "Home", videos });
  });
  return res.end();
};

이처럼 μž‘μ„±ν•œ ν›„ localhost:4000으둜 λ“€μ–΄κ°€λ©΄, 흰색 빈 νŽ˜μ΄μ§€κ°€ λœ¬λ‹€.
μ•žμ—μ„œ videoλ₯Ό μ°ΎκΈ°κΉŒμ§€(데이터λ₯Ό μ „μ†‘λ°›κΈ°κΉŒμ§€) μ‹œκ°„μ΄ κ±Έλ¦¬λ―€λ‘œ λ’€μ˜ μ½”λ“œμΈ res.end()κ°€ Video.find()의 콜백 ν•¨μˆ˜λ³΄λ‹€ λ¨Όμ € μ‹€ν–‰λœλ‹€.
λΉ„λ””μ˜€ μ°ΎκΈ°κ°€ λλ‚œ ν›„ 콜백 ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•˜λ €κ³  ν•˜λ©΄ 이미 ν•œλ²ˆ 응닡을 ν–ˆμœΌλ―€λ‘œ λ‹€μ‹œ μƒˆλ‘œμš΄ 응닡인 res.render()을 μ‹€ν–‰ν•  수 μ—†κ²Œ λ˜λŠ” 것이닀.


3) upload video

(1) video 데이터 생성 ν›„ λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν•˜κΈ°

μ‚¬μš©μžλŠ” videoλ₯Ό upload ν•  λ•Œ video의 title, description, hashtagsλ₯Ό μž‘μ„±ν•΄μ•Ό ν•œλ‹€. (meta λ°μ΄ν„°λŠ” μžλ™μœΌλ‘œ 주어진닀.)

  • 미리 λ§Œλ“€μ–΄λ‘” videoSchema에 따라 μ‚¬μš©μžκ°€ 데이터λ₯Ό μ„œλ²„λ‘œ 보낼 수 μžˆλ„λ‘ upload.pug 파일의 from νƒœκ·Έλ₯Ό μˆ˜μ •ν•œλ‹€.
//- upload.pug

form(method="POST")
  input(name="title", placeholder="Title", required)
  input(name="description", placeholder="Description", required) //- μΆ”κ°€ ❗
  input(name="hashtags", placeholder="Hashtags, separated by comma", required) //- μΆ”κ°€ ❗
  input(value="save", type="submit")
  • videoλ₯Ό μ—…λ‘œλ“œν•˜κΈ° μœ„ν•΄μ„œλŠ”(λ§Œλ“€κΈ° μœ„ν•΄μ„œλŠ”) document(JavaScript Object)λ₯Ό λ§Œλ“€μ–΄μ•Ό ν•œλ‹€.
    μ—¬κΈ°μ„œ documentλž€ Video λͺ¨λΈμ„ μ΄μš©ν•΄ λ§Œλ“ , 데이터λ₯Ό 가진 video μΈμŠ€ν„΄μŠ€λ₯Ό λ§ν•œλ‹€.
    video μΈμŠ€ν„΄μŠ€λŠ” μ‚¬μš©μžκ°€ form에 μž…λ ₯ν•œ 데이터(req.body)λ₯Ό λ°”νƒ•μœΌλ‘œ, 미리 λ§Œλ“€μ–΄λ‘” videoSchema와 같은 ν˜•νƒœλ₯Ό 가지도둝 λ§Œλ“€μ–΄μ Έμ•Ό ν•œλ‹€.
// Video.js (참고용으둜 μ μ–΄λ†“μŒ)
import mongoose from "mongoose";

const videoSchema = new mongoose.Schema({
  title: String,
  description: String,
  createdAt: Date,
  hashtags: [{ type: String }],
  mata: {
    views: Number,
    rating: Number,
  },
});

const Video = mongoose.model("Video", videoSchema);
export default Video;
// videoController.js (μˆ˜μ •)
import Video from "../models/Video";

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,
    },
  });
  console.log(video); // μƒμ„±ν•œ video μΈμŠ€ν„΄μŠ€λ₯Ό console 창에 좜λ ₯
  return res.redirect("/");
}

videoλ₯Ό λ§Œλ“€μ—ˆλ‹€ ! (아직 데이터λ₯Ό μ €μž₯ν•˜μ§€λŠ” μ•Šμ€ μƒνƒœ)
console μ°½μ—λŠ” λ‹€μŒκ³Ό 같은 κ²°κ³Όκ°€ 좜λ ₯λœλ‹€.

{
  title: 'video 1',
  description: 'first video',
  createdAt: 2021-11-26T09:24:15.248Z,
  hashtags: [ '#wow', '#amazing', '#boom' ],
  mata: { views: 0, rating: 0 },
  _id: new ObjectId("61a0a7bf1c1dbbe8315b6cdf")
}

β€» idλŠ” mongooseκ°€ video document에 μžλ™μœΌλ‘œ λΆ€μ—¬ν•΄μ€€ 고유 식별 λ²ˆν˜Έμ΄λ‹€.

  • 이제 videoλ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν•΄μ•Ό ν•œλ‹€.
    μ•„μ§κΉŒμ§€ λ°μ΄ν„°λŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ μ„Έκ³„μ—λ§Œ μ‘΄μž¬ν•  뿐 databaseμ—λŠ” μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€.
    Video.save()λ₯Ό μ΄μš©ν•΄ λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν•œλ‹€.
// videoController.js
import Video from "../models/Video";

export const postUpload = async (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,
    },
  });
  await video.save(); // μΆ”κ°€ ❗
  return res.redirect("/");
}

데이터λ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— μ „μ†‘ν•˜λŠ” λ°λŠ” μ‹œκ°„μ΄ 걸리기 λ•Œλ¬Έμ— async & await을 μ¨μ€˜μ•Ό ν•œλ‹€.
이에 따라 μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” video.save()에 μ˜ν•΄ 데이터가 λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯되기λ₯Ό κΈ°λ‹€λ¦°λ‹€.
cf. video.save()λŠ” λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯된 video documentλ₯Ό return ν•œλ‹€.

이제 localhost:4000으둜 λ“€μ–΄κ°€μ„œ upload videoλ₯Ό 클릭해 form을 μž‘μ„±ν•œ ν›„ save λ²„νŠΌμ„ λˆ„λ₯΄λ©΄
(λ°μ΄ν„°λ² μ΄μŠ€μ— video 데이터가 μ €μž₯된 ν›„) home으둜 redirect λ˜λŠ”λ°
(home μ»¨νŠΈλ‘€λŸ¬μ— μ˜ν•΄) μ΄λ•Œ homeμ—λŠ” λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯된 video 데이터가 ν‘œμ‹œλœλ‹€.

λ°μ΄ν„°λ² μ΄μŠ€μ— video 데이터가 μ €μž₯λ˜μ—ˆλ‹€! ( 즉, videoκ°€ upload λ˜μ—ˆλ‹€! )

πŸ’‘ MongoDB shellμ—μ„œ λ°μ΄ν„°λ² μ΄μŠ€ ν™•μΈν•˜κΈ°

show dbs β†’ wetube λ°μ΄ν„°λ² μ΄μŠ€κ°€ μƒμ„±λœ 것을 확인할 수 μžˆλ‹€.
admin   0.000GB
config  0.000GB
local   0.000GB
wetube  0.000GB

use wetube β†’ wetube λ°μ΄ν„°λ² μ΄μŠ€λ‘œ μ΄λ™ν•œλ‹€
switched to db wetube

show collections β†’ documents λ¬ΆμŒμ„ 보여쀀닀.
videos β†’ μ§€κΈˆμ€ document μ’…λ₯˜κ°€ video밖에 μ—†λ‹€.

helpλ₯Ό μž…λ ₯ν•˜λ©΄ μ—¬λŸ¬ λͺ…령어듀이 μ•ˆλ‚΄λ˜μ–΄ λ°μ΄ν„°λ² μ΄μŠ€ μ•ˆμ„ μ‚΄νŽ΄λ³Ό 수 μžˆλ‹€.

ex)
db.mycoll.find() β†’ ν•΄λ‹Ή collection μ•ˆμ˜ object듀을 확인할 수 μžˆλ‹€.

db.videos.find()
{ "_id" : ObjectId("61a0b09e66a8f06d2a0ad89b"), "title" : "video 1", "description" : "first video", "createdAt" : ISODate("2021-11-26T10:02:06.719Z"), "hashtags" : [ "#wow", "#first", "#video" ], "mata" : { "views" : 0, "rating" : 0 }, "__v" : 0 }

πŸ’‘ ν•œνŽΈ, 데이터λ₯Ό λ§Œλ“€κ³  μ €μž₯ν•˜λŠ” 데 Video.create({ }) λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

// videoController.js
import Video from "../models/Video";

export const postUpload = async (req, res) => {
  const { title, description, hashtags } = req.body;
  await Video.create({ // μˆ˜μ • ❗
    title,
    description,
    createdAt: Date.now(),
    hashtags: hashtags.split(",").map(word => `#${word}`);
    meta: {
      views: 0,
      rating: 0,
    },
  });
  return res.redirect("/");
}

(2) Mongoose Validation (Mongoose μœ νš¨μ„± 검사)

MongooseλŠ” 미리 μ•Œλ €μ€€ 데이터 νƒ€μž…μ„ λ°”νƒ•μœΌλ‘œ 데이터 νƒ€μž…μ˜ μœ νš¨μ„± 검사λ₯Ό 도와쀀닀.

  • μŠ€ν‚€λ§ˆμ— μ •μ˜λœ 데이터 νƒ€μž…κ³Ό μ‹€μ œ λ°μ΄ν„°μ˜ 데이터 νƒ€μž…μ΄ λ‹€λ₯΄λ©΄, μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.

    ex. postUpload μ»¨νŠΈλ‘€λŸ¬μ—μ„œ createdAt의 속성 κ°’μœΌλ‘œ λ¬Έμžμ—΄μ„ 적어주면, console 창에 ν•΄λ‹Ή μ—λŸ¬κ°€ μ°νžˆλ©΄μ„œ λΈŒλΌμš°μ €κ°€ 'λ¬΄ν•œ λ‘œλ”©'을 ν•œλ‹€.
    β†’ await으둜 인해 데이터 생성 및 μ €μž₯이 λλ‚œ 후에야 res.redirect() 즉, 응닡을 ν•  수 μžˆλŠ”λ° 데이터 생성 μžμ²΄κ°€ λ˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” λ‹€μŒ λ‹¨κ³„λ‘œ λ„˜μ–΄κ°€μ§€ μ•ŠμœΌλ―€λ‘œ λΈŒλΌμš°μ €κ°€ λ¬΄ν•œ λ‘œλ”©ν•˜κ²Œ λ˜λŠ” 것이닀.

  • μŠ€ν‚€λ§ˆμ— μ •μ˜λœ λ°μ΄ν„°μ˜ ꡬ성 μš”μ†Œκ°€ μ‹€μ œ 데이터 ꡬ성 μ‹œ λˆ„λ½λ˜λ©΄, μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.

    κ·ΈλŸ¬λ‚˜, μ§€κΈˆμ˜ κ²½μš°μ—λŠ” postUpload μ»¨νŠΈλ‘€λŸ¬μ—μ„œ μƒˆλ‘œμš΄ videoλ₯Ό 생성할 λ•Œ createdAt의 값이 λˆ„λ½λ˜μ—ˆλ”λΌλ„ μ—λŸ¬λŠ” λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€. μ΄λŠ” videoSchemaμ—μ„œ createdAt이 ν•„μˆ˜ μš”μ†Œ(required)κ°€ μ•„λ‹ˆκΈ° λ•Œλ¬Έμ΄λ‹€.
    λ”°λΌμ„œ, Video.js νŒŒμΌμ—μ„œ videoSchemaλ₯Ό μˆ˜μ •ν•΄ createAt을 ν•„μˆ˜ μš”μ†Œλ‘œ μ„€μ •ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

// Video.js
import mongoose from "mongoose";

const videoSchema = new mongoose.Schema({
  title: String,
  description: String,
  createdAt: { type: Date, required: true }, // μˆ˜μ • ❗
  hashtags: [{ type: String }],
  mata: {
    views: Number,
    rating: Number,
  },
});

const Video = mongoose.model("Video", videoSchema);
export default Video;

이제 postUpload μ»¨νŠΈλ‘€λŸ¬μ—μ„œ μƒˆλ‘œμš΄ videoλ₯Ό 생성할 λ•Œ createdAt의 값이 λˆ„λ½λ˜λ©΄, console 창에 ν•΄λ‹Ή μ—λŸ¬κ°€ μ°νžˆλ©΄μ„œ μ—­μ‹œ λΈŒλΌμš°μ €κ°€ λ¬΄ν•œ λ‘œλ”©μ„ ν•˜κ²Œ λœλ‹€.


  • 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ try catch ꡬ문을 μΆ”κ°€ν•˜μ—¬ μ—λŸ¬ λ°œμƒ μ‹œ μ‚¬μš©μžμ—κ²Œ 직접 μ—λŸ¬ λ‚΄μš©μ„ λ³΄μ—¬μ£Όκ³ μž ν•œλ‹€.
//- upload.pug μˆ˜μ •

block content
  if errorMessage
    span=errorMessage
// videoController.js μˆ˜μ •
try {
  await Video.create({
    title,
    description,
    createdAt: Date.now(),
    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 ν•˜λŠ” νŽ˜μ΄μ§€λ₯Ό λ³΄μ—¬μ£Όλ©΄μ„œ μ‚¬μš©μžμ—κ²Œ μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό ν•¨κ»˜ 보여쀀닀.
  // error._messageλŠ” μ½˜μ†” 창에 뜬 errorλ₯Ό μ°Έκ³ ν•΄ μž‘μ„±ν•œ 것
}

  • ν•œνŽΈ, videoController.js νŒŒμΌμ—μ„œ videoλ₯Ό 생성할 λ•Œ λ°˜λ³΅μ„ 쀄이기 μœ„ν•΄ μ• μ΄ˆμ— videoSchema에 default 값을 μ„€μ •ν•  수 μžˆλ‹€.
    (default 값을 μ„€μ •ν•˜λ©΄ μ—λŸ¬κ°€ 뜰 일이 μ—†μœΌλ‹ˆ requiredλŠ” 쑰금 μ“Έλͺ¨μ—†μ–΄μ§€μ§€λ§Œ κ·Έλž˜λ„ μ μ–΄μ£Όμ—ˆλ‹€.)
// Video.js
const videoSchema = new mongoose.Schema({
  title: String,
  description: String,
  createAt: { type: Date, required: true, default: Date.now }, // μˆ˜μ • ❗
  // μ΄λ•Œ Date.now() 라고 μž‘μ„±ν•˜λ©΄ ν•¨μˆ˜λ₯Ό λ‹Ήμž₯ μ‹€ν–‰μ‹œν‚¨λ‹€. Date.now 라고 μž‘μ„±ν•΄μ•Ό ν•œλ‹€.
  hashtags: [{ type: String }],
  mata: { // μˆ˜μ • ❗
    views: { type: Number, required: true, default: 0 },
    rating: { type: Number, required: true, default: 0 },
  },
});

이제 μ΄λ ‡κ²Œ μ„€μ •ν•΄λ†“μœΌλ©΄ video 데이터λ₯Ό λ§Œλ“€ λ•Œ createdAtκ³Ό meta 데이터λ₯Ό ꡳ이 적어주지 μ•Šμ•„λ„ μ—λŸ¬κ°€ λœ¨μ§€ μ•ŠλŠ”λ‹€!


  • μ΅œμ’… Video.js 파일 & videoController.js 파일의 postUpload 컨트둀러
// Video.js
import mongoose from "mongoose";

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 }],
  mata: {
    views: { type: Number, required: true, default: 0 },
    rating: { type: Number, required: true, default: 0 },
  },
});

const Video = mongoose.model("Video", videoSchema);
export default Video;
// videoController.js
export const postUpload = async (req, res) => {
  const { title, description, hashtags } = req.body;
  try {
    await Video.create({
      title,
      description,
      hashtags: hashtags.split(",").map((word) => `#${word}`),
    });
  } catch (error) {
    return res.render("upload", {
      pageTitle: "Upload Video",
      errorMessage: error._message,
    });
  }
  return res.redirect("/");
};

(3) κ·Έ μ™Έ μŠ€ν‚€λ§ˆ(Schema)의 λ‹€μ–‘ν•œ μ˜΅μ…˜λ“€

Mongoose - SchemaTypes μ°Έκ³ 

// Video.js
const videoSchema = new mongoose.Schema({
  title: { type: String, required: true, trim: true, maxLength: 80 },
  description: { type: String, required: true, trim: true, minLength: 20 },
  createdAt: { type: Date, required: true, default: Date.now },
  hashtags: [{ type: String, trim: true }],
  mata: {
    views: { type: Number, required: true, default: 0 },
    rating: { type: Number, required: true, default: 0 },
  },
});
  • trim

    ν…μŠ€νŠΈ μ–‘ μ˜†μ˜ spaceλ₯Ό μ‚­μ œν•΄μ€€λ‹€.

  • maxLength, minLength

    μ΅œλŒ€, μ΅œμ†Œ κΈ€μž μˆ˜λŠ” form의 input μ†μ„±μœΌλ‘œλ„ μ„€μ •ν•  수 μžˆμ§€λ§Œ, μ΄λŠ” νŽ˜μ΄μ§€μ˜ HTML μ½”λ“œλ₯Ό 톡해 μ‰½κ²Œ 변경이 κ°€λŠ₯ν•˜λ―€λ‘œ μ‹œμŠ€ν…œμ  보호λ₯Ό μœ„ν•΄ λ°μ΄ν„°λ² μ΄μŠ€μ—λ„ λ˜‘κ°™μ΄ μ„€μ •ν•΄μ€˜μ•Ό ν•œλ‹€.

(4) Home의 video κ΄€λ ¨ μ½”λ“œ μˆ˜μ • 및 μ—λŸ¬ ν•΄κ²°

  • video.pug νŒŒμΌμ—μ„œ video mixin을 μˆ˜μ •ν–ˆλ‹€.
    이제 Homeμ—μ„œλŠ” upload 된 videoλ“€μ˜ title, description, createdAt μ •λ³΄λ§Œμ„ λ³Ό 수 μžˆλ‹€.
//- video.pug

mixin video(video)
  div 
    h4
      a(href=`/videos/${video.id}`)=video.title
    p=video.description
    small=video.createdAt
    hr

  • Home에 λ‚˜μ—΄λœ video의 title을 ν΄λ¦­ν•˜λ©΄, 화면에 Cannot GET ~~~ 이 λœ¬λ‹€.

μ—λŸ¬ 원인

upload 된 video의 title을 클릭해 λ“€μ–΄κ°„ νŽ˜μ΄μ§€μ˜ url을 μ‚΄νŽ΄λ³΄λ©΄
videos/ 뒀에 μ˜€λŠ” id νŒŒλΌλ―Έν„°μ— μˆ«μžμ™€ λ¬Έμžκ°€ μ„žμ—¬μžˆλŠ” 것을 확인할 수 μžˆλ‹€.
κ·ΈλŸ¬λ‚˜ videoRouter.js νŒŒμΌμ— λ”°λ₯΄λ©΄ id νŒŒλΌλ―Έν„°κ°€ 숫자일 κ²½μš°μ—λ§Œ watch μ»¨νŠΈλ‘€λŸ¬κ°€ μ‹€ν–‰λ˜λ―€λ‘œ νŽ˜μ΄μ§€λ₯Ό κ°€μ Έμ˜¬ 수 μ—†μ–΄ μ—λŸ¬κ°€ 뜬 것이닀.

μ—λŸ¬ ν•΄κ²° 방법 1

id νŒŒλΌλ―Έν„°μ— 숫자만 올 수 μžˆλ„λ‘ μΆ”κ°€ν•΄μ€€ μ •κ·œν‘œν˜„μ‹(\\d+)을 μ‚­μ œν•œλ‹€.
λ‹€λ§Œ, μ΄λ ‡κ²Œ μˆ˜μ •ν•˜λ©΄ /uploadμ—μ„œ upload도 id νŒŒλΌλ―Έν„°λ‘œ μΈμ‹λ˜λŠ” μ—λŸ¬κ°€ λ°œμƒν•˜κΈ° λ•Œλ¬Έμ— /upload νŽ˜μ΄μ§€λ₯Ό μ–»λŠ” μ½”λ“œλ₯Ό /:id νŽ˜μ΄μ§€λ₯Ό μ–»λŠ” μ½”λ“œλ³΄λ‹€ μœ„μͺ½μ— μž‘μ„±ν•΄μ•Ό ν•œλ‹€.

// VideoRouter.js
videoRouter.route("/upload").get(getUpload).post(postUpload);
videoRouter.get("/:id", watch);
videoRouter.route("/:id/edit").get(getEdit).post(postEdit);

μ—λŸ¬ ν•΄κ²° 방법 2

MongoDB documentationμ—μ„œ id에 λŒ€ν•΄ 쑰사해 이λ₯Ό μ°Έκ³ ν•˜μ—¬ μ •κ·œν‘œν˜„μ‹μ„ λ§Œλ“ λ‹€.

MongoDBκ°€ μ œκ³΅ν•˜λŠ” idλŠ” 24λ°”μ΄νŠΈ 16μ§„μˆ˜ λ¬Έμžμ—΄λ‘œ 이루어져 μžˆλ‹€.
16μ§„μˆ˜ λ¬Έμžμ—΄μ΄λž€ 0λΆ€ν„° 9κΉŒμ§€μ˜ 10개의 μˆ«μžμ™€ AλΆ€ν„° FκΉŒμ§€μ˜ 6개의 문자둜 이루어진 λ¬Έμžμ—΄μ„ λ§ν•œλ‹€.

이λ₯Ό μ •κ·œν‘œν˜„μ‹μ„ μ΄μš©ν•΄ λ‚˜νƒ€λ‚΄λ©΄ [0-9a-f]{24}이 λœλ‹€.
μ΄λŠ” 0λΆ€ν„° 9 그리고 aλΆ€ν„° fκΉŒμ§€μ˜ 24자짜리 λ¬Έμžμ—΄μ„ μ˜λ―Έν•œλ‹€.

이λ₯Ό id νŒŒλΌλ―Έν„° 뒀에 μΆ”κ°€ν•΄μ•Ό ν•œλ‹€.

// videoRouter.js
videoRouter.get("/:id([0-9a-f]{24})", watch);
videoRouter.route("/:id([0-9a-f]{24})/edit").get(getEdit).post(postEdit);
videoRouter.route("/upload").get(getUpload).post(postUpload);

  • κ·ΈλŸ¬λ‚˜ μ΄λ ‡κ²Œ μž‘μ„±ν•˜λ©΄ Homeμ—μ„œ video의 title을 ν΄λ¦­ν–ˆμ„ λ•Œ, watch μ»¨νŠΈλ‘€λŸ¬κ°€ μž‘λ™μ€ ν•˜μ§€λ§Œ, video.pug 파일의 video λ³€μˆ˜κ°€ undefinedλΌμ„œ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.

    이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ videoController.js 파일의 watch μ»¨νŠΈλ‘€λŸ¬μ—μ„œ λ°μ΄ν„°λ² μ΄μŠ€λ‘œλΆ€ν„° video 데이터λ₯Ό 받아와 이λ₯Ό watch.pug 파일둜 λ Œλ”λ§ ν•΄μ•Ό ν•œλ‹€.

Model.findOne()
μž…λ ₯ν•œ λͺ¨λ“  쑰건을 λ§Œμ‘±ν•˜λŠ” 데이터λ₯Ό μ°Ύμ•„μ€€λ‹€.

Model.findById()
ν•΄λ‹Ή idλ₯Ό κ°€μ§€λŠ” 데이터λ₯Ό μ°Ύμ•„μ€€λ‹€.

// videoController.js
export const watch = async (req, res) => {
  const { id } = req.params;
  const video = await Video.findById(id);
  // const video = await Video.findById(id).exec();
  // .exec()을 뒀에 덧뢙이면, mongoose λ‚΄λΆ€μ μœΌλ‘œ promiseλ₯Ό return ν•œλ‹€
  // μ—¬κΈ°μ„œλŠ” async & await을 μ‚¬μš©ν–ˆμœΌλ―€λ‘œ μ•ˆ μ¨μ€˜λ„ λœλ‹€.
  return res.render("watch", { pageTitle: video.title, video });
};

이걸둜 userλŠ” Homeμ—μ„œ λͺ¨λ“  video듀을 λ³Ό 수 있게 λ˜μ—ˆκ³ , 각각의 video의 상세 νŽ˜μ΄μ§€μ—λ„ 접속할 수 있게 λ˜μ—ˆλ‹€.


  • μ‚¬μš©μžκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 24λ°”μ΄νŠΈ 16μ§„μˆ˜ λ¬Έμžμ—΄ idλ₯Ό μž…λ ₯ν•΄ video νŽ˜μ΄μ§€λ₯Ό λ°©λ¬Έν•˜λ©΄ μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄μ„œ λΈŒλΌμš°μ €λŠ” λ¬΄ν•œ λ‘œλ”©ν•˜κ²Œ λœλ‹€.

    이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μœ„μ™€ 같은 μ—λŸ¬ λ°œμƒ μ‹œ 404 νŽ˜μ΄μ§€λ₯Ό λ Œλ”λ§ν•˜λ„λ‘ ν•œλ‹€.
    views 폴더 μ•ˆμ— 404.pug νŒŒμΌμ„ λ§Œλ“  ν›„ base.pug νŒŒμΌμ„ extends ν•œλ‹€.

// videoController.js
export const watch = async (req, res) => {
  const { id } = req.params;
  const video = await Video.findById(id);
  if (video) {
    return res.render("watch", { pageTitle: video.title, video });
  }
  return res.render("404", { pageTitle: "Video not found" }); // μ—λŸ¬
};

+base.pug νŒŒμΌμ— Home으둜 κ°€λŠ” 링크λ₯Ό μΆ”κ°€ν–ˆλ‹€.


✨ 내일 ν•  것

  1. κ°•μ˜ 계속 λ“£κΈ°
profile
λŠ₯λ™μ μœΌλ‘œ μ‚΄μž, ν–‰λ³΅ν•˜κ²ŒπŸ˜

0개의 λŒ“κΈ€