DB connection, mongoDB, mongoosh(1)

김종민·2022년 10월 24일
0

Youtube

목록 보기
6/23

들어가기
DB연결 및 사용 방법을 알아본다.


https://mongoosejs.com/docs/guides.html
공식문서는 대단히 중요함.

1-1. src/db.js(DB연결)

import mongoose from 'mongoose'
///mongoose를 import한다

mongoose.connect('mongodb://127.0.0.1:27017/jmtube', {
  useNewUrlParser: true,
})
///cmd창에서 mongosh를 치면, 나오는 주소를 입력한다.
///(mongodb://127.0.0.1:27017/jmyube)

const db = mongoose.connection

const handleOpen = () => console.log('Connected to DB') 
///콜백으로 넣어도 되고, 만들어서 넣어도 되고

db.on('error', (error) => console.log('DB error', error)) 
///on은 여러번, once 한번

db.once('open', handleOpen)
///DB에 연결되면, Connected to DB가 찍힌다.

1-2. src/server.js

import './db'
///맨 위에 ./db를 import해 준다.

import Video from './models/Video'
import express from 'express'
import morgan from 'morgan'
import globalRouter from './routes/globalRouter'
import userRouter from './routes/userRouter'
import videoRouter from './routes/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('/videos', videoRouter)
app.use('/users', userRouter)

app.listen(PORT, () => console.log('Server start in 4000'))

2-1. src/models/Video.js(create schema)

import mongoose from 'mongoose' ///시작은 import mongoose

// export const formatHashtags = (hashtags) =>
//   hashtags.split(',').map((word) => (word.startsWith('#') ? word : `#${word}`))
--->DB에 hashtags가 save되기전에 단어를 분리하고 앞에 #을 붙여주는 함수.

///mongo는 document형식으로 하나의 전체 묶음으로 입력되므로
///schema생성 문법을 잘 본다.
const videoSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    upppercase: true, ///무조건 대문자로 입력되게
    trim: true, ///빈 칸은 자동적으로 없애주게
    maxLength: 140, ///최대 입력 글자.
  },
  description: String,
  createdAt: { type: Date, required: true, default: Date.now },
  ///기본값은 현재시간. 뒤에 ()이 있으면, 입력값과 중복실행됨.
  hashtags: [{ type: String, trim: true }], ///배열로 설정, 
  meta: {
    views: { type: Number, default: 0, required: true },
    rating: { type: Number, default: 0, required: true },
  },
})

///.static는 DB에 입력되기 전 middleware를 만든것임.
///formatHashtags라는 middleware로 hashtags를 분리, 입력함.
///맨 위의 함수로 만들어도 되고, 아래 아래방법을 이용해도 되지만,
///static가 가장 깔끔함, 공식문서 참조할 것!!
videoSchema.static('formatHashtags', function (hashtags) {
  return hashtags
    .split(',')
    .map((word) => (word.startsWith('#') ? word : `#${word}`))
})

// videoSchema.pre('save', async function () {
//   console.log('we saving this.', this)
//   this.hashtags = this.hashtags[0]
//     .split(',')
//     .map((word) => (word.startsWith('#') ? word : `#${word}`))
// })
///pre방법도 나쁘지는 않음.

const Video = mongoose.model('Video', videoSchema)
export default Video
///위에서 만든 videoSchema를 Video로 만들어서 export함.

3. src/controllers/videoController.js

import Video from '../models/Video' 
///가장 먼저 사용할 DB를 import한다.

// const handleSearch = (error, videos) => {
//   console.log('error', error)
//   console.log('videos', videos)
// }

export const trending = async (req, res) => {
  try {
    const videos = await Video.find({}).sort({ createdAt: 'desc' }) //asc
    ///await는 DB에서 load를 할 경우, load가 끝난 다음에 next를 실행하게
    ///하는 명령어, await가 쓰이면, 반드시 위에 asyn를 붙여준다.
    ///.sort(desc, asc)는 DB에서 load한 data 배열순서를 지정(최신data부터~)
    return res.render('home', { pageTitle: 'Home', videos })
    ///db로 부터 load한 data를 videos에 담아, home.pug에 videos로 넘겨준다.
  } catch (error) {
    return res.render('server-error', { error })
    ///error가 있을시 try, catch로 처리함.
  }
}
///홈 path에서 DB(Video)를 불러줌

///Video Detail 페이지
export const seeVideo = async (req, res) => {
  const { id } = req.params ///req.params로 id를 받아옴.
  const video = await Video.findById(id) 
  ///findById로 Video DB에서 video를 찾아서 return해 줌.
  
  if (!video) {
    return res.render('404', { pageTitle: 'Video not found' })
  }
  ///video가 없을때, 404 페이지로 redirect함.
  
  return res.render('watch', { pageTitle: video.title, video })
  ///watch 페이지에 id로 찾은 video를 return해줌.
}

///Edit Video 페이지 controller.(GET요청)
export const getEdit = async (req, res) => {
  const { id } = req.params 
  ///id 받아옴
  const video = await Video.findById(id)
  ///받아온 id로 edit 함 video 찾음.
  if (!video) {
    return res.render('404', { pageTitle: 'Video not found' })
  }
  ///video없을 경우 404 페이지로 redirect시킴
  return res.render('edit', { pageTitle: 'Edit Video', video })
  /// 'videoRouter.route('/:id([0-9a-f]{24})/edit').get(getEdit).post(postEdit)
  /// 위의 path로 req가 오면, edit.pug로 get method로 요청처리함.
}

///Edit Video 페이지 controller.(POST요청)
export const postEdit = async (req, res) => {
  const { id } = req.params 
  ///id 받아옴.
  const { title, description, hashtags } = req.body
  ///edit.pug에서 보낸 argument(title, description, hashtags)들을
  ///req.body로 받음.
  
  const video = await Video.exists({ _id: id }) 
  ///exists명령어는 id로 video를 찾아서, true, false로 return함
  
  if (!video) {
    return res.render('404', { pageTitle: 'Video not found' })
  }
  ///video가 없을 시, 404페이지로 return함.
  
  await Video.findByIdAndUpdate(id, {
    title,
    description,
    hashtags: Video.formatHashtags(hashtags),
  })
  ///findByIdAndUpdate는 id로 video를 찾고, 찾은 video를 update(edit)함.
  ///hashtags의 formatHashTags는 Video.js에서 만들어 준, static임.
  ///이렇게 사용된다는 것을 잘 본다.
  
  // video.title = title
  // video.description = description
  // video.hashtags = hashtags
  //   .split(',')
  //   .map((word) => (word.startsWith('#') ? word : `#${word}`))
  // await video.save()
  ---->findByIdAndUpdate를 사용안하고 썡으로 올리는 코딩, 참고만 할 것!
  
  return res.redirect(`/videos/${id}`)
  ///update(edit)가 끝났으면, videoDetail page로 redirect한다.
}

///videoRouter.route('/:id([0-9a-f]{24})/delete').get(deleteVideo)
///  위 path의 controller. /delete path의 get Controller.
/// findByIdAndDelete명령어로 id로 찾아서 찾은 video를 DB에서 삭제.
///삭제 후, 홈 path로 redirect함.
export const deleteVideo = async (req, res) => {
  const { id } = req.params
  await Video.findByIdAndDelete(id)
  return res.redirect('/')
}

///videoRouter.route('/upload').get(getUpload).post(postUpload)
///위 path의 /upload path의 get Controller. upload.pug 페이지 rendering
export const getUpload = (req, res) => {
  return res.render('upload', { pageTitle: 'Upload Video' })
}

///videoRouter.route('/upload').get(getUpload).post(postUpload)
///위 path의 /upload path의 post controller. 
export const postUpload = async (req, res) => {
  const { title, description, hashtags } = req.body
  ///req.body로 upload.pug에서 보낸 argument(title, description, hashtags)
  ///를 받음.
  
  ///DB에 입력하는 것이므로, await를 사용함.
  try {
    await Video.create({
      title,
      description,
      hashtags: Video.formatHashtags(hashtags),
    })
    return res.redirect('/')
    ///try~catch로 Video DB에 video를 upload하고, 홈 path로
    ///redirect함.
    ///여기서도 Video.js에서 만든 formatHashtags라는 static사용함.
  } catch (error) {
    console.log(error)
    return res.render('upload', {
      pageTitle: 'Upload Video',
      errorMessage: error._message,
    })
  }
  ///error발생시, upload.pug 페이지에, error message를 보내주는데,
  ///console에 보면 error message는 error._message로 찍힘.

  // const dbVideo = await video.save()
  //console.log(dbVideo)
  ---->위의 Video.create방법이 아닌 다른 방법.
  ---->입력된 video를 console에 찍어 보고싶을떄, 사용.
}

///globalRouter.get('/search', search)
///위의 path로 get 요청시 적용되는 controller
///search path는 globalRouter에 존재함. 
///위와 같이 router에 /:id 와 같은 것이 붙은것이 아니면,
///search.pug에서 검색어(keyword)를 입력했을때
///localhost:4000/search?keyword=avengers 와 같은 형식으로 url찍힘.(어밴져서검색시)
///참고로 req.ruery는 url data임.
export const search = async (req, res) => {
  const { keyword } = req.query
  ///req.query로 keyword 받아옴.
  let videos = []
  ///let으로 빈 배열의 videos를 만들어 놓음.
  if (keyword) {
    videos = await Video.find({
      title: {
        $regex:new RegExp(keyword, 'i')
      },
    })
  }
  ///seatch.pug에서 입력한 keyword로 Video DB에서 입력한
  ///keyword가 title에 포함된 video를 찾아서, videos에 담아줌.
  
  res.render('search', { pageTitle: 'Search', videos })
  ///찾은 videos들을 search.pug파일에 videos를 넘겨줌.
}

https://regexr.com/
search에서 title에 { $regex:new RegExp(keyword, 'i')}는 title중에
keyword가 포함된 video를 찾는다는 뜻. 밑의 공식문서를 참조하면 된다.

search관련 해서~~

videos title을 검색할때 keyword가 포함된것들을 regex operator를 통해 검색해 줄 수 있다.
(regex = regular expression의 약자)
const { keyword } = req.query;
.
.

  1. regex: new RegExp(keyword, "i") -> keyword가 포함된 것들을 검색.

  2. regex: new RegExp(^${keyword}, "i") -> keyword로 시작되는 것들을 검색.

  3. regex: new RegExp(${keyword}$, "i") -> keyword로 끝나는 것들을 검색.

-(여기서 "i" = Welcome,welcome 둘다 같게 해주는것 즉 lowercase,uppercase의 구분을 없게 해주는것)
( mongoose가 아닌 mongoDB가 해주는 기능이다)
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/RegExp

NOTICE!!! path의 id 관련해서~~
mongoDB에 save를 하면 return받는 id가 342lk34j23klh이런식인데,
이런 id를 js가 읽을 수 있게 바꿔주는것임([0-9a-f]{24})<--요게, 공식문서 참조

videoRouter.route('/:id([0-9a-f]{24})/delete').get(deleteVideo)

https://docs.mongodb.com/manual/reference/operator/query/regex/

정규표현식
https://www.regexpal.com

몽고DB regex ($regex)
몽고DB에서 정규표현식을 사용하기 위해 사용하는 키워드
쿼리의 패턴 일치 문자열에 대한 정규식 기능을 제공합니다.
https://docs.mongodb.com/manual/reference/operator/query/regex

RegExp mdn
RegExp 생성자는 패턴을 사용해 텍스트를 판별할 때 사용합니다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/RegExp

RegExp 사용 방법
RegExp 객체는 리터럴 표기법과 생성자로써 생성할 수 있습니다.
리터럴 표기법의 매개변수는 두 빗금으로 감싸야 하며 따옴표를 사용하지 않습니다.
생성자 함수의 매개변수는 빗금으로 감싸지 않으나 따옴표를 사용합니다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/RegExp

/ab+c/i 를 아래 RegExp 생성자를 이용해서 만들 수 있습니다.
new RegExp(/ab+c/, 'i') // 리터럴 표기법
new RegExp('ab+c', 'i') // 생성자 함수

profile
코딩하는초딩쌤

0개의 댓글