- Today
- Total
개발하는 고라니
[Node.js] MongoDB CRUD 본문
웹 개발을 약간이라도 경험해보았다면 데이터베이스를 다뤄보았을 것으로 기대된다. 보통 데이터베이스는 테이블, 컬럼 등을 사용하는 '관계형 데이터베이스 (RDBMS)'가 익숙할 것이다. 그런데 이번에 node를 공부하며, mongodb라는 NoSQL을 다뤄볼 기회가 생겨 몽고디비에 대해 간단하게 알아보고, 관계형 데이터베이스와의 차이점 몇 가지 그리고 node를 이용한 회원가입, 로그인, 정보 수정, 회원 탈퇴하는 아주 간단한 CRUD를 다뤄보고자 한다.
MongoDB
MongoDB는 C++로 만들어진 오픈소스 데이터베이스이며 NoSQL이다. 문서지향적 Cross-platform 데이터베이스이며 뛰어난 성능과 확장성을 지닌다.
MongoDB의 특징
- Document-Oriented Storage : 모든 데이터가 JSON 형태로 저장되며 스키마가 없다.
- Full Index Support : RDBMS에 뒤지지 않는 다양한 인덱싱 제공
- Replication & High Availability : 데이터 복제를 통한 가용성 향상
- Auto-Sharing : PK를 기반으로 여러 서버에 데이터를 나누는 scale-out이 가능
- Querying : Key 기반의 get, put 뿐 아니라 다양한 종류의 쿼리 제공
- Fast In-Peace Updates : 고성능 atomic operation 지원
- MapReduce
- GridFS
NoSQL ...?
NoSQL을 SQL을 사용하지 않는 것 이라 생각할 수 있다. 나 또한 그렇게 생각하고 있었으나, Not only SQL이라는 뜻이었다. 기존의 RDBMS의 한계를 극복하고자 생긴 새로운 형태의 데이터베이스이다. RDBMS처럼 고정된 스키마 및 JOIN이 존재하지 않는다.
NoSQL vs RDBMS
RDBMS | MongoDB |
Database | Database |
Table | Collection |
Tuple / Row | Document |
Column | Key / Field |
Table join | Embedded Documents |
Primary Key | Primary Key ( _id ) |
RDBMS
관계형 데이터베이스(RDBMS)는 키와 값들의 간단한 관계를 테이블화 시킨 매우 간단한 원칙의 전산정보 데이터베이스이다.
이 모델은 데이터를 행과 열을 이루는 하나 이상의 테이블로 정리. 테이블의 각 행에는 고유키 (PK)가 존재하며 이 고유키로 행을 식별한다. 행은 레코드나 튜플이라고도 불린다. 한 테이블 내의 행읜 고유키를 통해 다른 테이브들의 행으로 연결이 가능하다.
NoSQL
NoSQL은 단순히 기존 관계형 DBMS가 갖는 특성 뿐 아니라 다른 특성들을 부가적으로 지원하는 DBMS이다.
기존의 RDBMS보다 더 융통성있는 데이터 모델을 사용하고, 데이터의 저장 및 검색을 위한 특화된 매커니즘을 제공한다. 단순 검색 및 추가 작업에 있어 매우 최적화된 Key-Value 저장 기법을 사용한다. 응답속도나 처리 효율등에 매우 뛰어난 성능을 발휘한다.
최근에는 클라우드에서 제공하는 NoSQL이 다양한 분야에서 널리 활용되고 있다. 데이터가 일관성이 없고 복잡한 쿼리를 사용할 수 없는 환경에서 NoSQL이 스키마가 필요없고 데이터 분산이 용이하기 때문이다.
Document
Document라고 하면 문서를 떠올리기 쉽다. 하지만 NoSQL에서의 도큐먼트는 RDBMS에서의 레코드와 비슷한 개념이다. 이 데이터 구조는 한 개 이상의 Key-Value 쌍으로 이루어져있다. 다음은 몽고디비의 데이터 샘플이다.
{
"_id": ObjectId("5099803df3f4948bd2f98391"),
"username": "gorany",
"name": { first: "gorany.", last: "Dev" }
}
위의 형태는 JSON Object 형태인 것을 금방 알아차릴 수 있다. username, name, id_는 Key이고 오른쪽에 있는 것들은 Value이다.
id_는 12bytes의 hexadecimal 값으로, 각 document의 유일함(uniqueness)을 제공한다. 이 값의
첫 4bytes는 현재 timestamp,
다음 3bytes는 machine id,
다음 2bytes는 MongoDB 서버의 프로세스 ID,
마지막 3bytes는 순차번호이다. 추가 될 때마다 값이 증가한다는 것이다.
Document는 동적의 스키마를 갖는다. 같은 Collection 안에 있는 document끼리 다른 스키마를 갖고있을 수 있는데, 쉽게 말하면 서로 다른 데이터를 가질 수 있다는 뜻이다.
Collection
컬렉션은 몽고디비의 Document의 그룹이다. RDBMS에서의 테이블 개념이다. 하지만 테이블처럼 스키마를 갖지는 않는다.
여기까지 MongoDB라는 것이 무엇인지 아주 간략하게 알아보았다. 이제 node에서 mongoDB로의 CRUD를 하는 예제를 다뤄본다.
MongoDB 연결
우선 Node와 MongoDB를 연결하는 작업이 우선되어야 한다.
var http = require('http');
var express = require('express');
var mongoClient = require('mongodb').MongoClient;
var database;
function connectDB() {
var mongoURL = 'mongodb://localhost:27017/local';
mongoClient.connect(mongoURL, (err, db) => {
if(err) {
console.log('데이터베이스 연결 시도 실패');
return;
}
console.log('데이터베이스 연결 성공');
database = db;
}
}
...
/* 웹서버 생성하며 데이터베이스 연결 */
var server = http.createServer(app).listen(app.get('port'), () => {
console.log('서버 실행 성공');
connectDB();
}
Create - Signup
Database와 연결이 되었다면 회원가입 폼을 이용해 아이디, 이름, 패스워드를 POST요청 후 데이터베이스에 저장하는 것을 해보자.
//signup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/signup" method="post">
<div>
<input type="text" name="name">
</div>
<div>
<input type="text" name="id">
</div>
<div>
<input type="password" name="pw">
</div>
<button>Submit</button>
</form>
</body>
</html>
var bodyParser = require('body-parser');
var addUser = function(db, id, name, pw, callback) {
/* users 라는 컬렉션 가져옴 */
var users = db.collection('users');
/* users에 JSON Array를 insert */
users.insertMany([{
id: id,
name: name,
password: pw
}], (err, result) => {
if(err) {
console.log('Insert 도중 에러 발생');
callback(err, null);
}
if(result) {
console.log('회원가입 성공');
callback(null, result);
}
else
callback(null, null);
});
}
var app = express();
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
/* /signup 라우팅 */
router.get('/signup', (request, response) => {
console.log('# GET /signup');
fs.readFile('html/signup.html', (err, data) => {
if(err) throw err;
response.write(data);
response.end();
});
});
router.post('/signup', (request, response) => {
var body = request.body;
var id = body.id;
var pw = body.pw;
var name = body.name;
console.log(id +', ' + pw +', ' + name);
if(database){
addUser(database, id, pw, name, (err, result) => {
if(err) {
console.log('회원가입 중 오류 발생');
response.send('<h1>회원가입 중 오류 발생</h1>');
}
if(result) {
console.log('회원가입 성공');
response.redirect('/login');
}
});
}
else{
console.log('데이터베이스 연결 안됨.');
response.send('<h1>Database unconnected</h1>');
}
});
회원가입 폼에 test03 / test03 / 12345라는 값을 넣고 POST 요청을 보내보면 다음과 같은 로그를 볼 수 있다.
이로써 Create가 성공적으로 되었음을 알 수 있다.
Read - Login
로그인 폼을 통해 ID와 Password를 보내 데이터베이스에 일치하는 데이터가 있는지 확인하고, 있다면 로그인을 시키는 동작을 구현해보자.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/login" method="post">
<div>
<input type="text" name="id">
</div>
<div>
<input type="text" name="pw">
</div>
<button>Submit</button>
</form>
</body>
</html>
var authUser = function(db, id, pw, callback) {
if(db){
var users = db.collection('users');
users.find({
id: id,
pw: pw
}).toArray( (err, docs) => {
if(err) {
console.log('데이터베이스 검색 중 오류 발생');
callback(err, null);
}
if(docs.length > 0) {
console.log('일치하는 데이터 발견');
callback(null, result);
}
else{
console.log('일치하는 데이터 찾지 못함');
callback(null, null);
}
});
}
else{
console.log('데이터베이스가 연결되어있지 않음.');
}
}
...
router.route('/login').get((request, response) => {
console.log('# GET /login');
fs.readFile('html/login.html', 'utf8', (err, data) => {
if(err) throw err;
response.write(data);
response.end();
});
});
router.post('/login', (request, response) => {
console.log('# POST /login');
var body = request.body;
console.log(body);
var id = body.id;
const pw = body.pw;
console.log(id + ", " + pw);
if(database) {
authUser(database, id, pw, (err, docs) => {
if(err) {
console.log('에러발생');
response.end();
};
if(docs){
console.log(docs);
console.log('로그인 성공');
request.session.user = {
id: id,
authoirze:true
}
response.redirect('/mypage');
}
else{
console.log('로그인 실패');
response.redirect('/login');
}
});
}
});
아까 생성한 test03과 비밀번호 12345를 입력하고 제출하면 다음과 같은 로그를 만날 수 있다.
Update - Modify
이번엔 회원정보 수정을 간단하게 구현하며 데이터 수정을 해보자.
/* ID로 데이터 받아오는 함수 */
var getUser = (db, id, callback) => {
console.log('Get user()');
var users = db.collection('users');
users.find({id: id}).toArray((err, docs) => {
if(err) {
callback(err, null);
}
if(docs.length > 0){
callback(null, docs);
}
else{
callback(null, null);
}
});
}
/* Update 함수 */
var updateUser = (db, name, id, pw) => {
db.collection('users').updateOne(
{
id: id
},
{
$set: {
name: name,
id: id,
password: pw
}
});
}
/* modify Routing */
router.get('/modify', (request, response) => {
console.log('# GET /modify');
var id = request.session.user.id;
if(id) {
console.log('로그인 된 ID : ' + id);
getUser(database, id, (err, docs) => {
if(err) {
console.log('데이터 검색 중 오류 발생');
return;
}
if(docs){
var html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>회원정보 수정</h1>
<form action="/modify" method="post">
<div>
<label for="">Name</label>
<input type="text" name="name" value="${docs[0].name}">
</div>
<div>
<label for="">ID</label>
<input type="text" name="id" value="${docs[0].id}">
</div>
<div>
<label for="">Password</label>
<input type="password" name="pw" value="${docs[0].password}">
</div>
<button>Submit</button>
</form>
</body>
</html>
`;
response.write(html);
response.end();
}
});
}
else{
response.send('<h1>로그인이 되어있지 않습니다.</h1><a href="/login">로그인</a>');
}
});
router.post('/modify', (request, response) => {
console.log('# POST /modify');
var body = request.body;
var name = body.name;
var id = body.id;
var pw = body.pw;
if(database) {
console.log('회원 정보 수정');
updateUser(database, name, id, pw);
}
});
Delete - 회원 탈퇴
/modify에 회원탈퇴하기 위한 폼과 버튼을 만들었다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>회원정보 수정</h1>
<form action="/modify" method="post">
<div>
<label for="">Name</label>
<input type="text" name="name" value="${docs[0].name}">
</div>
<div>
<label for="">ID</label>
<input type="text" name="id" value="${docs[0].id}">
</div>
<div>
<label for="">Password</label>
<input type="password" name="pw" value="${docs[0].password}">
</div>
<button>Submit</button>
</form>
<form action="/drop" method="post">
<input type="hidden" value="${docs[0].id}" name="id">
<button>회원탈퇴</button>
</form>
</body>
</html>
회원탈퇴 버튼을 누르면 /drop으로 현재 ID가 body에 담겨 보내지고, 그 때 데이터베이스에서 해당 ID를 가진 데이터를 삭제하고 세션에서 로그인 정보를 지우는 것 까지 해보도록 한다.
var deleteUser = function (db, id, callback) {
db.collection('users').deleteOne(
{
id: id
},
(err, obj) => {
if(err) {
callback(err, null);
}
if(obj.result.n > 0){
callback(null, obj);
console.log(obj.result.n + ' document deleted!');
}
else{
callback(null, null);
console.log('0 document deleted!');
}
}
);
}
/* /drop post */
router.post('/drop', (request, response) => {
var id = request.body.id;
console.log('회원탈퇴할 ID : ' + id);
if(database) {
deleteUser(database, id, (err, obj) => {
if(err) {
console.log('회원 탈퇴 도중 에러 발생');
response.redirect('/modify');
}
if(obj){
console.log('회원 탈퇴 완료');
request.session.destroy((err) => {
if(err) throw err;
console.log('로그아웃 완료');
});
response.redirect('/login');
}
});
}
});
# Code </>
var express = require('express');
var http = require('http');
var static = require('serve-static');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var fs = require('fs');
var path = require('path');
var mongoClient = require('mongodb').MongoClient;
var multer = require('multer');
var cors = require('cors');
var database;
function connectDB() {
console.log('connectDB()');
var mongoURL = 'mongodb://localhost:27017/local';
mongoClient.connect(mongoURL, (err, db) => {
if(err) {
console.log('Error Occured');
return;
}
console.log('DB 할당 성공');
database = db.db('local');
});
}
var authUser = (db, id, password, callback) => {
console.log('AuthUser()');
var users = db.collection('users');
users.find({
id: id,
password: password
}).toArray((err, docs) => {
if(err) {
console.log('authuser error');
callback(err, null);
}
if(docs.length > 0){
console.log('일치하는 유저 존재');
callback(null, docs);
}
else{
console.log('일치하는 유저 X');
callback(null, null);
}
});
}
var addUser = function(db, id, password, name, callback) {
console.log('addUser() : ' + id +", " + password + ', ' + name);
var users = db.collection('users');
users.insertMany([{
id: id,
password: password,
name: name
}], (err, result) => {
if(err) {
callback(err, null);
return;
}
console.log('회원가입 성공');
console.log(result);
callback(null, result);
});
}
var getUser = (db, id, callback) => {
console.log('Get user()');
var users = db.collection('users');
users.find({id: id}).toArray((err, docs) => {
if(err) {
callback(err, null);
}
if(docs.length > 0){
callback(null, docs);
}
else{
callback(null, null);
}
});
}
var updateUser = (db, name, id, pw) => {
db.collection('users').updateOne(
{
id: id
},
{
$set: {
name: name,
id: id,
password: pw
}
});
}
var deleteUser = function (db, id, callback) {
db.collection('users').deleteOne(
{
id: id
},
(err, obj) => {
if(err) {
callback(err, null);
}
if(obj.result.n > 0){
callback(null, obj);
console.log(obj.result.n + ' document deleted!');
}
else{
callback(null, null);
console.log('0 document deleted!');
}
}
);
}
var app = express();
var router = express.Router();
app.set('port', process.env.PORT || 3000);
app.use(cors());
app.use(cookieParser());
app.use(session({
secret: 'My key',
resave: true,
saveUninitialized: true
}));
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
app.use('/public', static(path.join(__dirname, 'public')));
app.use('/upload', static(path.join(__dirname, 'upload')));
var storage = multer.diskStorage({
destination: (request, file, callback) =>{
callback(null, 'upload');
},
filename: (req, file, callback) => {
callback(null, Date.now() + "_" + file.originalname);
}
});
var upload = multer({
storage: storage,
limits: {
files: 5,
fileSize: 1024 * 1024 * 10
}
});
/* /signup 라우팅 */
router.get('/signup', (request, response) => {
console.log('# GET /signup');
fs.readFile('html/signup.html', (err, data) => {
if(err) throw err;
response.write(data);
response.end();
});
});
router.post('/signup', (request, response) => {
var body = request.body;
var id = body.id;
var pw = body.pw;
var name = body.name;
console.log(id +', ' + pw +', ' + name);
if(database){
addUser(database, id, pw, name, (err, result) => {
if(err) {
console.log('회원가입 중 오류 발생');
response.send('<h1>회원가입 중 오류 발생</h1>');
}
if(result) {
console.log('회원가입 성공');
response.redirect('/login');
}
});
}
else{
console.log('데이터베이스 연결 안됨.');
response.send('<h1>Database unconnected</h1>');
}
});
router.route('/login').get((request, response) => {
console.log('# GET /login');
fs.readFile('html/login.html', 'utf8', (err, data) => {
if(err) throw err;
response.write(data);
response.end();
});
});
router.post('/login', (request, response) => {
console.log('# POST /login');
var body = request.body;
console.log(body);
var id = body.id;
const pw = body.pw;
console.log(id + ", " + pw);
if(database) {
authUser(database, id, pw, (err, docs) => {
if(err) {
console.log('에러발생');
response.end();
};
if(docs){
console.log(docs);
console.log('로그인 성공');
request.session.user = {
id: id,
authoirze:true
}
response.redirect('/mypage');
}
else{
console.log('로그인 실패');
response.redirect('/login');
}
});
}
});
/* modify Routing */
router.get('/modify', (request, response) => {
console.log('# GET /modify');
var id = request.session.user.id;
if(id) {
console.log('로그인 된 ID : ' + id);
getUser(database, id, (err, docs) => {
if(err) {
console.log('데이터 검색 중 오류 발생');
return;
}
if(docs){
var html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>회원정보 수정</h1>
<form action="/modify" method="post">
<div>
<label for="">Name</label>
<input type="text" name="name" value="${docs[0].name}">
</div>
<div>
<label for="">ID</label>
<input type="text" name="id" value="${docs[0].id}">
</div>
<div>
<label for="">Password</label>
<input type="password" name="pw" value="${docs[0].password}">
</div>
<button>Submit</button>
</form>
<form action="/drop" method="post">
<input type="hidden" value="${docs[0].id}" name="id">
<button>회원탈퇴</button>
</form>
</body>
</html>
`;
response.write(html);
response.end();
}
});
}
else{
response.send('<h1>로그인이 되어있지 않습니다.</h1><a href="/login">로그인</a>');
}
});
router.post('/modify', (request, response) => {
console.log('# POST /modify');
var body = request.body;
var name = body.name;
var id = body.id;
var pw = body.pw;
if(database) {
console.log('회원 정보 수정');
updateUser(database, name, id, pw);
}
});
router.post('/drop', (request, response) => {
var id = request.body.id;
console.log('회원탈퇴할 ID : ' + id);
if(database) {
deleteUser(database, id, (err, obj) => {
if(err) {
console.log('회원 탈퇴 도중 에러 발생');
response.redirect('/modify');
}
if(obj){
console.log('회원 탈퇴 완료');
request.session.destroy((err) => {
if(err) throw err;
console.log('로그아웃 완료');
});
response.redirect('/login');
}
});
}
});
app.use('/', router);
app.all('*', (request, response) => {
response.status(404).write(fs.readFileSync('html/404.html', 'utf8'));
response.end();
});
var server = http.createServer(app).listen(app.get('port'), ()=> {
console.log('express server is running');
connectDB();
});
# References
m.blog.naver.com/shakey7/221558533513
www.youtube.com/watch?v=L4gf4Xp-_UA&list=PLG7te9eYUi7tHH-hJ2yzBJ9h6dwBu1FUy&index=40
www.w3schools.com/nodejs/nodejs_mongodb_find.asp
www.zerocho.com/category/MongoDB/post/579e2821c097d015000404dc
'Framework > Node.js' 카테고리의 다른 글
[Node.js] ejs (0) | 2021.05.01 |
---|---|
[Node.js] Mongoose (0) | 2021.04.26 |
[Node.js] 파일 업로드 (0) | 2021.04.24 |
[Node.js] 요청 라우팅 - Router (0) | 2021.04.23 |
[Node.js] Node.js Tutorial (3) - File System (0) | 2021.04.16 |