Skip to the content.

Web Programming Application 2023

한국교통대학교, 충주 | KNUT (Korea National University of Transportation)


Final Test / 기말고사

Schedule: 6월 16일 일정

Study Guide / 공부 가이드

Unit 3: MongoDB & Mongoose Introduction

MongoDB is a NoSQL, non-relational, database that stores data in BSON (binary JSON) documents.
몽고디비는 NoSQL, 비관계형 데이터베이스로 데이터를 BSON (바이너리 JSON) 문서에 저장합니다.

Basic MongoDB Shell commands

Mongoose

Mongoose is an object-document modeling, or object-document mapper, (ODM) library for MongoDB that allows you to run MongoDB commands in a way that preserves the object-oriented structure of your application.
몽구스는 몽고디비의 객체-문서 모델링, 또는 객체-문서 매퍼 (ODM) 라이브러리로, 애플리케이션의 객체 지향 구조를 보존하는 방식으로 몽고디비 명령을 실행할 수 있습니다.

Basic Mongoose commands

Mongoose Schema

A Mongoose schema defines the structure of the document, default values, validators, etc., whereas a Mongoose model provides an interface to the database for creating, querying, updating, deleting records, etc.
몽구스 스키마는 문서의 구조, 기본값, 유효성 검사기 등을 정의하는 반면, 몽구스 모델은 레코드 생성, 조회, 수정, 삭제 등을 위한 데이터베이스 인터페이스를 제공합니다.

const mongoose = require("mongoose"),
  Schema = mongoose.Schema;

const userSchema = new Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true },
});

module.exports = mongoose.model("User", userSchema);

Mongoose Schema Types

Mongoose Schema Options

Data Relationships

JavaScript Promises

A JavaScript Promise is an object that represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
자바스크립트 프로미스는 비동기 작업의 최종 완료 (또는 실패) 및 그 결과 값을 나타내는 객체입니다.

const promise = new Promise((resolve, reject) => {
  // do something
  if (/* success */) {
    resolve(/* value */);
  } else {
    reject(/* reason */);
  }
});

promise
  .then((value) => {
    // success
  })
  .catch((reason) => {
    // failure
  });

Unit 4: CRUD Operations with Mongoose

The four basic operations of persistent storage are Create, Read, Update, and Delete (CRUD).
지속적인 저장의 네 가지 기본 작업은 생성, 읽기, 수정 및 삭제 (CRUD)입니다.

Operation HTTP Method Mongoose Method Controller action
Create POST create() create()
Read GET find() index()
Update PUT update() update()
Delete DELETE delete() delete()

In order to use PUT and DELETE HTTP methods, we need to install and configure the method-override middleware.
PUT 및 DELETE HTTP 메서드를 사용하려면 method-override 미들웨어를 설치하고 구성해야 합니다.

const methodOverride = require("method-override");
app.use(
  methodOverride("_method", {
    methods: ["POST", "GET"],
  })
);

Then, we can use the _method query parameter to override the HTTP method.
그런 다음 _method 쿼리 매개변수를 사용하여 HTTP 메서드를 재정의할 수 있습니다.

<form method="POST" action="/users/<%= user.id %>/update?_method=PUT">
  <!-- ... -->
</form>

CRUD Controller Actions

Note: The controller actions below are examples. You will need to modify them to fit your application. The book shows these actions in Lesson 21, from pages 301-320. Pay close attention to the subscribersController.js code from pages 317-320.

참고: 아래 컨트롤러 액션은 예제입니다. 애플리케이션에 맞게 수정해야 합니다. 책에서는 21강, 301-320쪽에서 이러한 액션을 보여줍니다. 317-320쪽의 subscribersController.js 코드에 주의를 기울이십시오.

CREATE

These actions are used to create new documents in the database.
이러한 액션은 데이터베이스에 새 문서를 생성하는 데 사용됩니다.

Three important things to remember when creating a new document using a Submit form are:
제출 양식을 사용하여 새 문서를 생성할 때 기억해야 할 세 가지 중요한 사항은 다음과 같습니다.

  1. The form’s action attribute must be set to the route that will handle the form submission.
    • 양식의 action 속성은 양식 제출을 처리할 경로로 설정해야 합니다.
  2. The form’s method attribute must be set to POST.
    • 양식의 method 속성은 POST로 설정해야 합니다.
  3. The form’s input elements must have name attributes that match the names of the document’s properties.
    • 양식의 input 요소는 문서의 속성 이름과 일치하는 name 속성을 가져야 합니다.
<form action="/users" method="POST">
  <input type="text" name="username" />
  <input type="password" name="password" />
  <button type="submit">Submit</button>
</form>

Create Controller Actions

const User = require("../models/user");

module.exports = {
  new: (req, res) => {
    res.render("users/new");
  },

  create: (req, res, next) => {
    // getUserParams() 함수가 있다면 여기에서 사용할 수 있습니다.
    User.create(req.body) // getUserParams()를 사용했다면 반환 값을 전달합니다.
      .then((user) => {
        res.locals.redirect = "/users"; // redirect to the '/users' route
        res.locals.user = user;
        next();
      })
      .catch((err) => {
        console.log(err); // handle error
        next(err);
      });
  },
};

Because we are passing a redirect variable to res.locals, we also need to add a redirect handling middleware.
res.localsredirect 변수를 전달하기 때문에 리디렉션 처리 미들웨어를 추가해야 합니다.

module.exports = {
  redirectView: (req, res, next) => {
    let redirectPath = res.locals.redirect;
    if (redirectPath) res.redirect(redirectPath);
    else next();
  },
};

READ

These actions are used to retrieve documents from the database.
이러한 액션은 데이터베이스에서 문서를 검색하는 데 사용됩니다.

Read Controller Actions

const User = require("../models/user");

module.exports = {
  index: (req, res, next) => {
    User.find()
      // populate().exec()는 여기에서 사용할 수 있습니다.
      .then((users) => {
        res.locals.users = users; // pass the 'users' object to the next middleware
        next();
      })
      .catch((err) => {
        console.log(err); // handle error
        next(err);
      });
  },

  indexView: (req, res) => {
    res.render("users/index"); // render the 'users/index' template
  },

  show: (req, res, next) => {
    User.findById(req.params.id)
      // populate()는 여기에서 사용할 수 있습니다.
      .then((user) => {
        // 여기에서 속성을 변경하고 .save()합니다.
        res.locals.users = users; // pass the 'users' object to the next middleware
        next();
      })
      .catch((err) => {
        console.log(err); // handle error
        next(err);
      });
  },

  showView: (req, res) => {
    res.render("users/show"); // render the 'users/show' template
  },
};

UPDATE

These actions are used to modify documents in the database. They are similar to the CREATE actions.
이러한 액션은 데이터베이스의 문서를 수정하는 데 사용됩니다. CREATE 액션과 유사합니다.

Update Controller Actions

const User = require("../models/user");

module.exports = {
  edit: (req, res, next) => {
    User.findById(req.params.id)
      // populate()는 여기에서 사용할 수 있습니다.
      .then((user) => {
        res.render("users/edit", { user }); // render the 'users/edit' template with the 'user' object
      })
      .catch((err) => {
        console.log(err); // handle error
        next(err);
      });
  },

  update: (req, res, next) => {
    // getUserParams() 함수가 있다면 여기에서 사용할 수 있습니다.
    let userParams = getUserParams(req.body); // example

    User.findByIdAndUpdate(req.params.id, {
      $set: userParams,
    })
      // populate()는 여기에서 사용할 수 있습니다.
      .then((user) => {
        res.locals.redirect = `/users/${userId}`; // redirect to the '/users/:id' route
        res.locals.user = user;
        next();
      })
      .catch((err) => {
        console.log(err); // handle error
        next(err);
      });
  },
};

DELETE

These actions are used to remove documents from the database.
이러한 액션은 데이터베이스에서 문서를 제거하는 데 사용됩니다.

Delete Controller Actions

const User = require("../models/user");

module.exports = {
  delete: (req, res, next) => {
    User.findByIdAndRemove(req.params.id) // 여기에서 `.findByIdAndDelete()`를 사용할 수도 있다.
      .then(() => {
        res.locals.redirect = "/users"; // redirect to the '/users' route
        next();
      })
      .catch((err) => {
        console.log(err); // handle error
        next(err);
      });
  },
};

Unit 5: User Authentication with Passport

Flash Messages with Connect-Flash

Flash messages are messages that are stored in the session and then rendered to the user. They are typically used to display success or error messages.
플래시 메시지는 세션에 저장한 다음 사용자에게 렌더링되는 메시지입니다. 일반적으로 success 또는 error 메시지를 표시하는 데 사용됩니다.

const session = require("express-session"),
  flash = require("connect-flash");

router.use((req, res, next) => {
  res.locals.flashMessages = req.flash();
  next();
});

// Controller action
module.exports = {
  create: (req, res, next) => {
    User.create(req.body)
      .then((user) => {
        req.flash("success", "User created successfully!"); // set the flash message
        res.redirect("/users");
      })
      .catch((err) => {
        req.flash("error", err.message); // set the flash message
        next(err);
      });
  },
};

Password Hashing with Bcrypt

bcrypt is a password hashing function that is used to hash passwords before storing them in the database.
bcrypt는 데이터베이스에 저장하기 전에 비밀번호를 해시하는 데 사용되는 비밀번호 해시 함수입니다.

We usually use the bcrypt.hash() function to hash passwords before saving them in the database with a pre-save hook.We use the bcrypt.compare() function to compare passwords when logging in users.
일반적으로 사전 저장 후크로 데이터베이스에 저장하기 전에 비밀번호를 해시하는 데 bcrypt.hash() 함수를 사용합니다. 사용자 로그인 시 비밀번호를 비교하는 데 bcrypt.compare() 함수를 사용합니다.

const bcrypt = require("bcrypt");

userSchema.pre("save", function (next) {
  const user = this;
  bcrypt
    .hash(user.password, 10)
    .then((hash) => {
      user.password = hash;
      next();
    })
    .catch((err) => {
      console.log(err); // handle error
      next(err);
    });
});

// Controller action
module.exports = {
  authenticate: (req, res, next) => {
    User.findOne(email: req.body.email)
      .then((user) => {
        if (!user) {
          req.flash("error", "User not found!");
          res.redirect("/login");
        } else {
          bcrypt
            .compare(req.body.password, user.password)
            .then((result) => {
              if (result) {
                req.session.userId = user.id;
                res.redirect("/users");
              } else {
                req.flash("error", "Password is incorrect!");
                res.redirect("/login");
              }
            })
            .catch((err) => {
              console.log(err); // handle error
              next(err);
            });
        }
      })
      .catch((err) => {
        console.log(err); // handle error
        next(err);
      });
  },
}

Passport

Passport is an authentication middleware for Node.js that supports authentication using a username and password, Facebook, Twitter, and more.
Passport는 Node.js용 인증 미들웨어로, 사용자 이름과 비밀번호, Facebook, Twitter 등을 사용한 인증을 지원합니다.

Passport Configuration

const passport = require("passport");
router.use(passport.initialize());
router.use(passport.session());

// serialize user = save user ID to session / 사용자 ID를 세션에 저장
const User = require("../models/user");
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

Passport Local Strategy

We use passport-local-mongoose to add a local strategy to our User model.
passport-local-mongoose를 사용하여 User 모델에 로컬 전략을 추가합니다.

const passportLocalMongoose = require("passport-local-mongoose");
userSchema.plugin(passportLocalMongoose, {
  usernameField: "email",
});

Local Variables

We also need to add the loggedIn and currentUser variables to the res.locals object so that they are available in all templates.
모든 템플릿에서 사용할 수 있도록 res.locals 객체에 loggedIncurrentUser 변수를 추가해야 합니다.

router.use((req, res, next) => {
  res.locals.loggedIn = req.isAuthenticated();
  res.locals.currentUser = req.user;
  next();
});

Passport Local Authentication

Finally, we rewrite create and authenticate controller actions to use Passport local authentication. We also add a logout controller action to log out users.
마지막으로, Passport 로컬 인증을 사용하도록 createauthenticate 컨트롤러 액션을 다시 작성합니다. 또한 사용자 로그아웃을 위해 logout 컨트롤러 액션을 추가합니다.

const passport = require("passport");

module.exports = {
  create: (req, res, next) => {
    User.register(new User({ email: req.body.email }), req.body.password)
      .then((user) => {
        passport.authenticate("local")(req, res, () => {
          req.flash("success", "User created successfully!");
          res.redirect("/users");
        });
      })
      .catch((err) => {
        req.flash("error", err.message);
        next(err);
      });
  },

  login: (req, res) => {
    res.render("users/login");
  },

  authenticate: (req, res, next) => {
    passport.authenticate("local", {
      successRedirect: "/users",
      failureRedirect: "/login",
      failureFlash: true,
    })(req, res, next);
  },

  logout: (req, res, next) => {
    req.logout();
    res.redirect("/login");
  },
};

Important Packages

We are using the following Node.js packages in our project. Be sure you understand what they do and how to use them.
프로젝트에서 다음 Node.js 패키지를 사용합니다. 이들이 무엇을 하는지와 사용 방법을 반드시 이해하십시오.