Todo 리스트 + MongoDB

김주언·2022년 5월 29일
0

WEB - Basic

목록 보기
6/10

MongoDB?

MongoDB는 개발 및 확장이 용이하도록 설계된 문서 데이터베이스

NoSQL
- “NoSQL 데이터베이스”란 비관계형 데이터베이스를 지칭한다.
- non SQL(비 SQL)“ 또는 “not only SQL(SQL만을 사용하지 않는)”의 약자
- NoSQL 데이터베이스가 관계형 데이터베이스 이외의 형식으로 데이터를 저장하는 데이터베이스

문서 데이터베이스
MongoDB의 레코드는 필드와 값 쌍으로 구성된 데이터 구조인 문서이다. MongoDB 문서는 JSON 객체와 유사하며, 필드 값에는 다른 문서, 배열 및 문서 배열이 포함될 수 있다.

컬렉션/뷰/주문형 구체화된 뷰
MongoDB는 문서를 컬렉션에 저장한다. 컬렉션은 관계형 데이터베이스의 테이블과 유사하다.

설치 / 실행

$ xcode-select --install
$ brew tap mongodb/brew
$ brew install mongodb-community@5.0

설치 시 아래 항목들을 포함한다.

  • The mongod server
  • The mongos sharded cluster query router
  • The MongoDB Shell, mongosh

추가적으로 다음 파일들을 생성한다.

파일명위치
configuration file/usr/local/etc/mongod.conf
log directory/usr/local/var/log/mongodb
data directory/usr/local/var/mongodb

mongod 프로세스 실행
MongoDB를 brew를 사용하여 macOS에서 실행하거나, 수동으로 백그라운드 프로세스로 실행할 수 있다. macOS 서비스로 실행하도록 한다

$ brew services start mongodb-community@5.0

위의 명령어를 실행하면 mongod 프로세스를 실행한다.

$ brew services stop mongodb-community@5.0

종료 명령어

실행 확인하기

$ brew services list

mongodb-community 가 출력되면 정상 실행중이다.

mongosh
MongoDB 사용시작을 위해 셸에 연결한다.
새로운 터미널 창을 열고 mongosh 입력

MongoDB Database Tools
brew를 통해 MongoDB를 설치하면 MongoDB Database Tools도 설치된다.
MongoDB Database Tools은 커맨드라인 유틸리티 집합이다. MongoDB 서버를 설치하면 바로 커맨드라인에서 사용가능하다.

CRUD 작업

데이터베이스 전환

db : 셸 내에서 현재 데이터베이스 표시

use <db> : 데이터 베이스 전환. 전환 전에 DB 생성할 필요 없다. MongoDB는 첫 데이터 저장 시 데이터베이스를 생성한다.

Insert

  • db.<collection 이름>.insertOne()
  • db.<collection 이름>.insertMany()
    movie 컬렉션에 문서 하나 삽입하기
db.movies.insertOne(
   {
      title: 'Titanic',
      year: 1997,
      genres: [ 'Drama', 'Romance' ],
      rated: 'PG-13',
      languages: [ 'English', 'French', 'German', 'Swedish', 'Italian', 'Russian' ],
   }

Read
컬렉션 내부의 문서 찾기

  • db.<collection 이름>.find()


age가 18보다 큰 문서의 name과 address 속성을 읽는다. projection부분에 0을 주면 해당 속성은 제외한다.

Update

  • db.<collection 이름>.updateOne()

  • db.<collection 이름>.updateMany()

  • db.<collection 이름>.replaceOne()

age가 18이하인 문서의 status를 업데이트한다.

Delete

  • db.<collection 이름>.deleteOne()
  • db.<collection 이름>.deleteMany()

status가 "reject"인 문서 삭제

쿼리 연산자
$eq , $gt와 같이 $를 사용하여 쿼리를 선택할 수 있다.


✔️ TODO 리스트에 DB 추가하기

카테고리별 할일 관리 할 수 있도록 구현한다.
디폴트 카테고리는 Today

Mongoose

node.js에서 사용하는 mongodb 객체 모델링 모듈
mongoDB를 node환경에서 조작하는 API를 제공한다.

$ npm install mongoose --save

DB 연결하기

mongod 서버가 열려있어야 한다!
app.js

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/todolistDB');

todolistDB 컬렉션을 생성하거나 연결한다.

mongoose CRUD 작업 기본

1. 스키마 생성

몽구스는 스키마가 기본단위이다. 스키마는 객체 형태로 생성한다.
각 스키마는 MongoDB 컬렉션에 매핑되고 해당 컬렉션 내 문서의 형식을 정의한다.

const itemsSchema = {
  name: String,
};

2. 모델 생성

: 모델은 일종의 생성자라고 보면된다. 모델의 인스턴스가 문서가 된다.

생성한 스키마를 모델로 컴파일해준다.
첫 번째 인수는 모델이 사용되는 컬렉션의 단수 이름. Mongoose는 자동으로 모델 이름의 복수형 소문자 버전을 찾는다. 따라서 아래의 예에서 Item 모델은 데이터베이스의 Item 컬렉션에 대한 것이다
두번째 인수로는 스키마를 전달한다.

const Item = mongoose.model('Item', itemsSchema);

3. 문서 작성

모델의 인스턴스를 문서라고 한다. new 키워드를 사용하여 생성한다.

const item1 = new Item({ name: 'todo list using mongoose!' });
const item2 = new Item({ name: 'Hit the + button to add a new item' });
const item3 = new Item({ name: 'Hit the checkbox to delete an item' });

4. 쿼리

: 모델 클래스에서 제공하는 CRUD 메소드들을 쿼리라고 한다.

const defaultItems = [item1, item2, item3];

Item.insertMany(defaultItems, (err) => {
  if (err) {
    console.log('Error');
  } else {
    console.log('Success');
  }
});

위 코드는 컬렉션에 문서를 삽입하는 쿼리이다. 첫번째 인수로 DB에 삽입할 데이터, 두번째 인수로 에러에 대한 콜백함수

코드를 실행하고 mongoDB를 확인해보면

  • todolistDB 가 생성됨 (DB)
  • items라는 이름으로 컬렉션이 생성됨 (DB 내부에)
  • 컬렉션에 문서가 삽입됨

_id 는 사용자가 지정해주지 않으면 자동으로 할당된다.

문서 저장

<문서>.save()

app.post('/', (req, res) => {
  const input = req.body.newItem;
  const item = new Item({ name: input });
  item.save();
  res.redirect('/');
});

위 코드는 사용자 입력값을 클라이언트로부터 전달받아서 이를 이용하여 문서 인스턴스를 생성하고, 해당 문서를 컬렉션에 저장한다. 저장 후 redirect를 통해 화면 새로고침 효과

체크 시 삭제 구현

문서 삭제

문서의 아이디를 이용하여 문서 삭제하기
: <Model>.findByIdAndRemove(<Id>, callbackFn(err))
콜백 함수 지정하지 않으면 boolean 값만 리턴한다.

list.ejs

<div class="box">
  <% newItems.forEach(item => { %>
  <form action="/delete" method="post">
    <div class="item">
      <input
        type="checkbox"
        onchange="this.form.submit()"
        name="checkbox"
        value="<%=item._id %>"
      />
      <p><%= item.name %></p>
    </div>
  </form>
  <% }) %>
  1. list.ejs파일에 form 태그 추가하고 action, method 지정
  2. 체크박스가 체크 되는 것 감지하기 위해 input 태그에 onchange 속성을 지정
  3. 서버쪽으로 보낼 때 식별을 위한 name 태그 지정

체크박스를 체크한 후 서버쪽에서 클라이언트의 req.body를 받아보면 아래와 같이 출력된다.

{ checkbox: 'on' }
  1. 식별을 위해서 value 속성을 추가하는데, item의 id를 이용한다.
  2. findByIdAndRemove() 메서드로 아이템 삭제하기

app.js

app.post('/delete', (req, res) => {
  const checkedId = req.body.checkbox;
  Item.findByIdAndRemove(checkedId, (err) => {
    if (err) {
      console.log('error!');
    } else {
      console.log('document deleted!');
      res.redirect('/');
    }
  });
});

리스트 카테고리

카테고리 생성

라우트 파라미터 사용

사용자가 URL에 카테고리 이름을 입력하면 해당 입력값으로 컬렉션을 생성한다.

app.js 내용 수정

const listSchema = {
  name: String,
  items: [itemsSchema],
};

const List = mongoose.model('List', listSchema);

// 콜론(:)을 이용하여 사용자 입력값 식별
app.get('/:customListName', (req, res) => {
  const customListName = req.params.customListName;
  
  // DB에 사용자 입력가 입력한 리스트명이 존재하는지 여부 확인
  List.findOne({ name: customListName }, (err, foundList) => {
    if (!err) {
      if (!foundList) {
        // DB에 없으면 리스트 새로 생성 후 기본 todo 아이템 삽입
        const list = new List({ name: customListName, items: defaultItems });
        list.save();
      } else {
        // DB에 이미 있으면 해당 리스트페이지 출력
        res.render('list', {
          listTitle: foundList.name,
          newItems: foundList.items,
        });
      }
    }
  });
});

URL의 루트 디렉터리 아래에 리스트이름을 입력한 후 mongosh 에서 컬렉션을 확인해보면 아래와 같이 list가 생성되어 있다.

todolistDB> show collections
items
lists
todolistDB>

카테고리별 할일 삽입

list.ejs

<form class="item" action="/" method="post">
    <input
      type="text"
      name="newItem"
      placeholder="할 일 추가하기"
      autocomplete="off"
    />
    <button type="submit" name="list" value="<%=listTitle %>">+</button>
  </form>
</div>
  • 버튼에 name 속성 추가해서 + 버튼 클릭시 서버로 넘겨주는 input 태그의 데이터 식별할 수 있도록 한다. (서버에서 req.body.list로 접근가능하다)
  • 카테고리 식별을 위해 버튼의 value 속성에 해당 카테고리의 이름을 삽입하여 서버로 전달한다.

app.js

app.post('/', (req, res) => {
  const input = req.body.newItem;
  const listName = req.body.list;
  const item = new Item({ name: input });

  if (listName === 'Today') {
    item.save();
    res.redirect('/');
  } else {
    List.findOne({ name: listName }, (err, foundList) => {
      foundList.items.push(item);
      foundList.save();
      res.redirect('/' + listName);
    });
  }
});
  • 카테고리 이름(listName)이 Today일 때는 단순 추가
  • 카테고리 이름이 Today가 아니면 DB에서 해당 카테고리 이름을 검색해서, 그 카테고리 List.items에 새로운 할일 추가해주고 (push) 저장한다. (save)
  • 카테고리 페이지로 리다이렉션

카테고리별 할일 삭제

카테고리별로 내용을 삽입한 후, 삭제하기 위해 체크박스를 클릭하면 실제로 삭제되지 않고 새로고침 시 다시 화면에 출력되는 문제 해결해야한다.

✔️ 카테고리 내부의 아이템의 id와 카테고리명이 필요하다
list.ejs

  <% newItems.forEach(item => { %>
  <form action="/delete" method="post">
    <div class="item">
      <input
        type="checkbox"
        onchange="this.form.submit()"
        name="checkbox"
        value="<%=item._id %>"
      />
      <p><%= item.name %></p>
    </div>
  </form>
  <% }) %>

/delete 폼 태그 내부에 아래 코드 추가

<input type="hidden" name="listName" id="<%= listTitle%>" />

어떤 카테고리에서 할일을 지워야하는지 식별하기 위해 id 속성으로 카테고리명을 지정한다. hidden 옵션으로 화면에 input 태그가 출력되지는 않지만, submit될 때 데이터는 전달 할 수 있게 된다.

app.js

app.post('/delete', (req, res) => {
  const checkedId = req.body.checkbox;
  const listName = req.body.listName;

  if (listName === 'Today') {
    Item.findByIdAndRemove(checkedId, (err) => {
      if (!err) {
        console.log('error!');
        res.redirect('/');
      }
    });
  } else {
    List.findOneAndUpdate(
      { name: listName },
      { $pull: { items: { _id: checkedId } } },
      (err) => {
        if (!err) res.redirect('/' + listName);
      }
    );
  }
});

Mongoose가 제공하는 findOneAndUpdate

A.findOneAndUpdate(conditions, update, callback) // executes

List 컬렉션 내부에서, name: listName 속성을 가지는 문서가 대상이다.

$pull: { items: { _id: checkedId } : listName을 이름으로 가지는 문서내부의 items 배열에 접근해서, _id값이 checkedId인 문서를 삭제한다.

  • $pull : 배열 업데이트 연산자, 지정된 조건과 일치하는 값의 모든 인스턴스를 기존 배열에서 제거한다.

콜백함수 실행을 통해 카테고리와 일치하는 Todo List 페이지로 돌아간다.

profile
학생 점심을 좀 차리시길 바랍니다

0개의 댓글