mongodbの定理


ドキュメントベース
分散型データベース
mongodbとして保存されているデータはjson-like-document(sqlなど)です.
文書内にCRUDを作成できます.

ターミナルの操作


  • モンゴルで使用
    mongo

  • 私が持っているdbビュー
    show dbs

  • 現在使用中のdbを確認
    db

  • 使用するdb
を選択
    use dbName
    (現在のレッスンはuse wetube)

  • dbコレクションビュー
    show collections

  • dbコレクションでのドキュメントの表示
    
 db.collectionName.find()

  • dbコレクションからすべてのドキュメントを削除
    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


    モデルはMongooseがMongodbデータがどのようなものかを教えてくれます
    そう言えばmongoseはデータの作成、削除、修正、検索に役立ちます.
    データモデルでは、ファイルの最初のアルファベットは大文字です.
    schemaはデータの形式です.
    無効なデータをドキュメントに書き込めません.
    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}`);
    };

    作成前に処理する関数=>Mongooseの前中間パッケージを使用

    // this는 새문서(새객체)를 나타냄.
    videoSchema.pre('save', async function () {
      this.hashtags = this.hashtags[0]
        .split(',')
        .map((word) => (word.startsWith('#') ? word : `#${word}`));
    });
    更新中のドキュメント(作成時)にアクセスできます.
    createに適用されますが、updateは適用されません.
    これは、更新のためのミドルウェアが必要であることを意味する.

    静的、更新可能な関数


    findByIdAndUpdate用のpreミドルウェアはありません.
    findByIdUpdate findOneAndUpdateを呼び出します.
    findOneAndUpdateはsave hookを呼び出しません.また、更新するドキュメントにアクセスできません.
    静的保存と更新
    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()
    この2つには本当に違いがあります.合理的な理由がない限り、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 });
    };

    検索


    mongoseには良いクエリーエンジンがあります.
    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にパスワードを保存しないでください.
    暗号化は重要です
    ハッシュは、一方向関数と文字列として表されます.絶対に取り返しがつかない.
    同じ入力値は常に同じ出力値です.ただし、出力値は入力値を特定できません.
    dbにハッシュパスワードが保存されます.
    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とは異なります).
    つまり、誰が送ってきたのか覚えていません.この状態を無状態と呼ぶ.
    だからexpress-sessionをインストールします
    npm i express-session
    server.jsで作成
    import session from 'express-session';
    
    app.use(
      session({
        secret: 'Hello!',
        resave: true,
        saveUninitialized: true,
      })
    );
    セッションというミドルウェアがブラウザにクッキーを送信
    これにより、サーバはブラウザを単独で覚えることができます.
    バックエンドはクッキーを受信しています.
    クッキーはバックエンドがブラウザに提供する情報です.
    クッキーには一定のルールがあるため、ブラウザはバックエンドにリクエストを発行するたびに、そのリクエストにクッキーを追加します.
    セッションidをユーザーにスケールします.
    セッションIDを入れた場所はクッキーです.
    セッションidはクッキーに格納されますが、データ自体はサーバに格納されます.
    ユーザーをログインさせるのはreqです.セッションオブジェクトに情報を入れる

    dbを更新する場合、セッションも個別に更新する必要があります.
    app.use((req, res, next) => {
      // 백엔드가 기억하고 있는 sessions(백엔드가 기억하고 있는 유저들)를 console.log해서 보는거임.
      req.sessionStore.all((error, sessions) => {
        console.log(sessions);
        next();
      });
    });

    pugテンプレートはローカルユーザーにアクセスできます.
    pugとexpressは、ローカルユーザーを共有できるように設定されています.
    変数をlocalsオブジェクトとしてテンプレートにグローバルに送信できます.
    localsオブジェクトはすべてのpugテンプレートにインポートされました.
    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メモリに格納され、RAMメモリは消費時に消失するため、mongoStoreとして指定すると、セッションはmongodbに保存されます.すなわち、消費時に消えるまでセカンダリメモリに格納されます.
    
    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' }),
      })
    );

    ログインした人にセッションIDを1つだけあげたいなら


    saveUninitialized : true
    値が設定されていない転送日セッションをリポジトリに保存し、返信などのセッション所有者にクッキーを渡します.
    saveUninitialized : false
    req.セッション内の値を変更した瞬間、セッションを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' }),
      })
    );

    ログアウトしたい時。


    セッションを破棄します.
    export const logout = (req, res) => {
      req.session.destroy();
      return res.redirect('/');
    };

    findByIdAndUpdate


    デフォルトではfindByIdAndUpdateは更新前のドキュメントを返し、オプション{new:true}を作成すると更新後のデータが返されます.