mongodb 정리

hrj2233·2021년 11월 17일
0

db

목록 보기
1/3

document를 기반
분산 데이터베이스
mongodb로 저장한 데이터는 json-like-document(sql처럼 행으로 된 데이터를 저장할 필요 x)
document 내부를 CRUD가 가능.

터미널 사용하기

  1. 몽고 사용하기


    mongo

  2. 내가 가진 db 보기


    show dbs

  3. 현재 사용 중인 db 확인


    db

  4. 사용할 db 선택하기


    use dbName
    (현재 수업에서는 use wetube)

  5. db 컬렉션 보기


    show collections

  6. db 컬렉션 안에 documents 보기

    
 db.collectionName.find()

  7. db 컬렉션 안에 documents 모두 제거하기


    db.collectionName.remove({})

mongoose

node.js와 mongoDB를 이어주는 패키지.

import mongoose from 'mongoose';

mongoose.connect('mongodb://127.0.0.1:27017/vsa', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

const db = mongoose.connection;

const handleOpen = () => console.log('✅ Connected to DB');
const handleError = (error) => console.log('❌ DB Error', error);

// on은 여러번, once는 한번만 실행.
db.on('error', handleError);
db.once('open', handleOpen);
import './db';

model

model은 mongoose가 mongodb에게 데이터들이 어떻게 생겼는지 알려주는거.
이렇게 알려주면 mongoose는 데이터를 만들고 삭제하고 수정하고 검색하는걸 도와줌.
data model의 파일의 첫글자는 대문자로 작성.
schema는 데이터의 형태.
올바르지 않은 데이터는 document에 기록될 수 없게 만듬.

import mongoose from 'mongoose';

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

// model의 이름, 데이터 형태 작성. 
const Video = mongoose.model('Video', videoSchema);
export default Video;

init.js

import './db';
import './models/Video';

query

import Video from '../models/Video';

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

async await

import Video from '../models/Video';

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

db에 저장하는 방법.

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('/');
};
export const postUpload = async (req, res) => {
  const { title, description, hashtags } = req.body;
  try {
    await Video.create({
      title,
      description,
      createdAt: Date.now(),
      hashtags: hashtags.split(',').map((word) => `#${word}`),
      meta: {
        views: 0,
        rating: 0,
      },
    });
  } catch (error) {
    return res.render('upload', {
      pageTitle: 'Upload Video',
      errorMessage: error._message,
    });
  }

  return res.redirect('/');
};

해당 객체 찾기.

export const watch = async (req, res) => {
  const { id } = req.params;
  const video = await Video.findById(id);
  return res.render('watch', { pageTitle: `Watching Home`, video });
};

해당 객체 찾아서 업데이트 하기.

export const postEdit = async (req, res) => {
  const { id } = req.params;
  const { title, description, hashtags } = req.body;
  const video = await Video.exists({ _id: id });
  if (!video) {
    return res.render('404', { pageTitle: 'Video not found.' });
  }
  await Video.findByIdAndUpdate(id, {
    title,
    description,
    hashtags: hashtags
      .split(',')
      .map((word) => (word.startsWith('#') ? word : `#${word}`)),
  });

  return res.redirect(`/videos/${id}`);
};

생성 전에 처리해야할 function의 필요성 => Mongoose의 pre Middleware 활용

// this는 새문서(새객체)를 나타냄.
videoSchema.pre('save', async function () {
  this.hashtags = this.hashtags[0]
    .split(',')
    .map((word) => (word.startsWith('#') ? word : `#${word}`));
});

update하는 문서(create할때)에 접근이 가능.

create에는 적용이 되지만 update는 적용 안됨.
즉 update를 위한 middleware가 필요.

static, 업데이트 할 수 있는 함수

findByIdAndUpdate를 위한 pre middleware는 없음.
findByIdAndUpdate는 findOneAndUpdate을 호출.
findOneAndUpdate은 save hook을 호출하지 않음. 그리고 업데이트 하려는 문서에 접근 할 수 없음.

static은 save와 update 가능

videoSchema.static('formatHashtags', function (hashtags) {
  return hashtags
    .split(',')
    .map((word) => (word.startsWith('#') ? word : `#${word}`));
});
export const postEdit = async (req, res) => {
  const { id } = req.params;
  const { title, description, hashtags } = req.body;
  const video = await Video.exists({ _id: id });
  if (!video) {
    return res.render('404', { pageTitle: 'Video not found.' });
  }
  await Video.findByIdAndUpdate(id, {
    title,
    description,
    hashtags: Video.formatHashtags(hashtags),
  });

  return res.redirect(`/videos/${id}`);
};

객체 삭제하기

export const deleteVideo = async (req, res) => {
  const { id } = req.params;
  await Video.findByIdAndDelete(id);
  return res.redirect('/');
};

Model.findOneAndDelete()
Model.findOneAndRemove()

이 둘은 정말 약간의 차이가 있는데 대부분의 상황에서 타당한 이유가 없는 한 delete를 사용하라고 되어 있음.

https://www.zerocho.com/category/MongoDB/post/579ecb1fc097d015000404dd

여기 글을 읽어보니 몽고 db는 롤백이 안되서 remove를 하면 다시 되돌릴 수 없기에 remove보다 delete를 사용하라고 권장하는듯

정렬하기


export const home = async (req, res) => {
  const videos = await Video.find({}).sort({ createdAt: 'desc' });
  return res.render('home', { pageTitle: 'Home', videos });
};

검색하기

mongoose는 훌륭한 쿼리엔진을 가지고 있음.

extends base.pug
include mixins/video

block content 
  form(method="GET")
    input(placeholder="Search by title", name="keyword", type="text")
    input(type="submit", value="Search now")
  
  div 
    each video in videos 
      +video(video)
export const search = (req, res) => {
  const { keyword } = req.query;
  let videos = [];
  if (keyword) {
    videos = await Video.find({
      title: {
        // mongodb 가 하는 거임.
        $regex: new RegExp(`${keyword}$`, 'i'),
      },
    });
  }
  return res.render('search', { pageTitle: 'Search', videos });
};

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

bcrypt

db에 password를 저장해서는 안됨.
암호화가 중요
해싱은 일방향 함수, 문자열로 표현됨. 절대 되돌릴 수 없음.
같은 입력값으로는 항상 같은 출력값이 나옴. 하지만 출력값으로는 입력값을 알아낼 수 없음.
db에 해싱된 password를 저장할 거임.

userSchema.pre('save', async function () {
  // 숫자는 해싱횟수(salt rounds), async await 쓰면 콜백 안써도 됨.
  this.password = await bcrypt.hash(this.password, 5);
});

compare

export const postLogin = async (req, res) => {
  const { username, password } = req.body;
  const pageTitle = 'Login';
  const user = await User.findOne({ username });
  if (!user) {
    return res.status(400).render('login', {
      pageTitle,
      errorMessage: 'An account with this username does not exists',
    });
  }
  // password를 해싱하고 user.password와 비슷한지 비교(해싱된 횟수는 user.password 앞에 있어서 알 수 있음. 그렇기 때문에 password를 해싱해서 user.password를 비교할 수 있는 거임.)
  const ok = await bcrypt.compare(password, user.password);
  if (!ok) {
    return res.status(400).render('login', {
      pageTitle,
      errorMessage: 'Wrong password',
    });
  }
  res.redirect('/');

$or

  const exists = await User.exists({ $or: [{ username }, { email }] });
  if (exists) {
    return res.render('join', {
      pageTitle,
      errorMessage: 'This username/email is already taken.',
    });
  }

참조:[$or]
https://docs.mongodb.com/manual/reference/operator/query/or/#mongodb-query-op.-or

status

400: bad request

클라이언트에서 발생한 에러 때문에 요청을 처리하지 못할때 쓰면 됨.

404: Not Found

서버가 요청한 페이지를 찾을 수 없을 때 발생한다. 서버에 존재하지 않는 페이지에 대한 요청이 있을 경우 서버는 이 코드를 제공한다.

  if (password !== password2) {
    return res.status(400).render('join', {
      pageTitle,
      errorMessage: 'Password confirmation does not match',
    });
  }

session

일반적으로 브라우저에게 요청하고 렌더링 되면 백엔드하고 연결이 끊김(계속 연결되는 wifi하고는 다름)
즉 누가 보냈는지 기억을 못함. 이런 상태를 stateless라고 함.

그래서 express-session을 설치할 거임

npm i express-session

server.js에 작성

import session from 'express-session';

app.use(
  session({
    secret: 'Hello!',
    resave: true,
    saveUninitialized: true,
  })
);

session이라는 middleware가 브라우저에 cookie를 전송
그렇게 함으로써 서버가 브라우저를 개별적으로 기억할 수 있음.
백엔드에서 cookie를 받고 있음.
cookie는 백엔드가 브라우저에게 주는 정보.
cookie는 정해진 규칙이 있기 때문에 매번 backend에 request를 할 때 브라우저는 알아서 그 request에 cookie를 덧붙이게 됨.
사용자에게 session id를 줌.
session id를 넣는 곳은 쿠키.
session id는 쿠키에 저장되지만 데이터 자체는 서버에 저장.
사용자를 로그인 시키는 것은 req.session object 안에 정보를 넣는거.

db 업데이트할때 세션도 따로 업데이트 해줘야함

app.use((req, res, next) => {
  // 백엔드가 기억하고 있는 sessions(백엔드가 기억하고 있는 유저들)를 console.log해서 보는거임.
  req.sessionStore.all((error, sessions) => {
    console.log(sessions);
    next();
  });
});

pug template은 locals에 접근할 수 있음.
pug랑 express가 서로 locals를 공유 할 수 있도록 설정되어 있음.
locals object로 template에 변수를 전역적으로 보낼수 있음.
locals object는 이미 모든 pug template에 import 된 object.

export const localsMiddleware = (req, res, next) => {
  //전역에서 사용 가능한 변수
  res.locals.loggedIn = Boolean(req.session.loggedIn);
  res.locals.siteName = 'VSA';
  res.locals.loggedInUser = req.session.user;
  console.log(req.session);
  console.log(res.locals);
  next();
};
import session from 'express-session';

app.use(localsMiddleware);

새로고침하면 사라짐
왜냐하면 서버가 다시 시작되면 MongoStore를 설치할 때까지 모든 세션이 사라집니다.

mongostore

npm i connect-mongo

세션은 디폴트로 RAM메모리에 저장되는데 렘메모리는 껏다키면 사라지니까 mongoStore로 지정해주면 몽고db 즉, 보조기억장치에 저장시켜서 껏다켜도 사라지지 않게 할 수 있음.


import MongoStore from 'connect-mongo';

app.use(
  session({
    secret: 'Hello!',
    resave: true,
    saveUninitialized: true,
    store: MongoStore.create({ mongoUrl: 'mongodb://127.0.0.1:27017/vsa' }),
  })
);

로그인한 사람만 session id를 주고 싶을 때

saveUninitialized : true

따로 값을 설정하지 않은 전달 받은 날 것의 세션을 즉시 Store에 저장 후 세션 주인에게 쿠키를 (답장하듯) 넘겨 준다.

saveUninitialized : false

req.session 속 값을 수정하는 그 순간에 세션을 Store에 저장 후 그제야 쿠키를 전달한다.

resave: true

세션이 변경되지 않았어도 저장

resave: false

세션이 변경되어야 저장

참조: https://github.com/expressjs/session#resave

import session from 'express-session';
import MongoStore from 'connect-mongo';

app.use(
  session({
    secret: 'Hello!',
    resave: false,
    saveUninitialized: false,
    store: MongoStore.create({ mongoUrl: 'mongodb://127.0.0.1:27017/vsa' }),
  })
);

logout 하고 싶을때

세션 없애면 됨.

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

findByIdAndUpdate

findByIdAndUpdate는 기본적으로 업데이트 되기 전의 문서를 리턴해 주었는데 옵션인 {new:true}를 작성하면 업데이트된 데이터를 리턴해 줌.

0개의 댓글

관련 채용 정보